diff --git a/.github/scripts/update_versions.py b/.github/scripts/update_versions.py index dcbd749796..1f593273bc 100644 --- a/.github/scripts/update_versions.py +++ b/.github/scripts/update_versions.py @@ -1,54 +1,79 @@ import os import json +import re +import xml.etree.ElementTree as ET from github import Github -def update_latest_versions(): - version = os.environ["GITHUB_REF"].split("/")[-1] +def get_target_release_branch(version): + """ + Extracts the first two numbers from the release version (`9.0.5` → `rel-9.0`) + to determine the corresponding `rel-x.x` branch. + """ + match = re.match(r"(\d+)\.(\d+)\.\d+", version) + if not match: + raise ValueError(f"Invalid version format: {version}") - if "rc" in version: - return False + major, minor = match.groups() + target_branch = f"rel-{major}.{minor}" + return target_branch - with open("latest-versions.json", "r") as f: - latest_versions = json.load(f) +def get_version_from_common_props(branch): + """ + Retrieves `Version` and `LeptonXVersion` from the `common.props` file in the specified branch. + """ + g = Github(os.environ["GITHUB_TOKEN"]) + repo = g.get_repo("abpframework/abp") - latest_versions[0]["version"] = version + try: + file_content = repo.get_contents("common.props", ref=branch) + common_props_content = file_content.decoded_content.decode("utf-8") - with open("latest-versions.json", "w") as f: - json.dump(latest_versions, f, indent=2) + root = ET.fromstring(common_props_content) + version = root.find(".//Version").text + leptonx_version = root.find(".//LeptonXVersion").text - return True + return version, leptonx_version + except Exception as e: + raise FileNotFoundError(f"common.props not found in branch {branch}: {e}") -def create_pr(): - g = Github(os.environ["GITHUB_TOKEN"]) - repo = g.get_repo("abpframework/abp") +def update_latest_versions(): + """ + Updates `latest-versions.json` based on the most relevant release branch. + """ + # Get the release version from GitHub reference + release_version = os.environ["GITHUB_REF"].split("/")[-1] # Example: "refs/tags/v9.0.5" → "v9.0.5" + if release_version.startswith("v"): + release_version = release_version[1:] # Convert to "9.0.5" format - branch_name = f"update-latest-versions-{os.environ['GITHUB_REF'].split('/')[-1]}" - base = repo.get_branch("dev") - repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=base.commit.sha) + # Determine the correct `rel-x.x` branch + target_branch = get_target_release_branch(release_version) + + # Retrieve `common.props` data from the target branch + version, leptonx_version = get_version_from_common_props(target_branch) - # Get the current latest-versions.json file and its sha - contents = repo.get_contents("latest-versions.json", ref="dev") - file_sha = contents.sha + # Skip if the version is a preview or release candidate + if "preview" in version or "rc" in version: + return False - # Update the file in the repo - repo.update_file( - path="latest-versions.json", - message=f"Update latest-versions.json to version {os.environ['GITHUB_REF'].split('/')[-1]}", - content=open("latest-versions.json", "r").read().encode("utf-8"), - sha=file_sha, - branch=branch_name, - ) + # Read the `latest-versions.json` file + with open("latest-versions.json", "r") as f: + latest_versions = json.load(f) - try: - pr = repo.create_pull(title="Update latest-versions.json", - body="Automated PR to update the latest-versions.json file.", - head=branch_name, base="dev") - except Exception as e: - print(f"Error while creating PR: {e}") + # Add the new version entry + new_version_entry = { + "version": version, + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": leptonx_version + } + } + + latest_versions.insert(0, new_version_entry) # Insert the new version at the top - pr.create_review_request(reviewers=["ebicoglu", "gizemmutukurt", "skoc10"]) + # Update the file + with open("latest-versions.json", "w") as f: + json.dump(latest_versions, f, indent=2) -if __name__ == "__main__": - should_create_pr = update_latest_versions() - if should_create_pr: - create_pr() \ No newline at end of file + return True diff --git a/Directory.Packages.props b/Directory.Packages.props index 042b00ec7a..c860d010c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,12 +15,13 @@ + - - - - + + + + @@ -37,6 +38,10 @@ + + + + @@ -111,7 +116,7 @@ - + diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index ea150de47e..d53323c3a2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -607,7 +607,9 @@ "OrganizationDoesNotHaveACreditCardInGateway": "Organization does not have a credit card in the gateway!", "Permission:EditWinners": "Edit Winners", "Permission:ChangeDrawingStatus": "Change Drawing Status", - "Menu:Licenses": "Licensing", + "Menu:LicenseSettings": "License Settings", + "Menu:Licensing": "Licensing", + "Menu:Campaigns": "Campaigns", "OrganizationId": "Organization Id", "RemoveAllWinnersConfirmationMessage": "Are you sure you want to remove all winners?", "AutoRenewals": "Auto Renewals", @@ -665,8 +667,27 @@ "EditAttendees": "Edit attendees", "ExportAttendeesAsExcel": "Export attendees as Excel", "DuplicateRaffle": "Duplicate raffle", + "LicenseMonthsOnNewPurchase": "License Months for New License", + "LicenseMonthsOnRenewPurchase": "License Months for License Renewal", + "SupportQuestionCountPerDeveloperOnRenewLicense": "Support Question Count Per Developer for License Renewal", + "SupportQuestionCountPerDeveloperOnNewLicense": "Support Question Count Per Developer for New License", + "IncludedDeveloperCount": "Included Developer Count", + "CanBuyAdditionalDevelopers": "Can Buy Additional Developers", + "HasEmailSupport": "Has Email Support", + "IsSupportPrivateQuestion": "Can Open Private Support Question", + "AdditionalDeveloperPrice": "Additional Developer Price", + "LicenseUpgradePrice": "License Upgrade Price", + "AdditionalDeveloperUpgradePrice": "Additional Developer Upgrade Price", + "EditLicense{0}": "Edit {0} License", + "CampaignNameAlreadyExists": "Campaign name already exists", + "DiscountRate": "Discount Rate", "Menu:RedisManagement": "Redis Management", "RedisManagement": "Redis Management", - "Permission:RedisManagement": "Redis Management" + "Permission:RedisManagement": "Redis Management", + "UserCleanUp": "User Clean Up", + "Permission:UserCleanUp": "User Clean Up", + "AllowPrivateQuestion": "Allow Private Question", + "Permission:Campaigns": "Campaigns", + "Permission:Licenses": "License Settings" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index 4856813fe1..b81733d247 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -645,7 +645,7 @@ "ExtendPaymentInfoSection_Description": "By extending/renewing your license, you will continue to get premium support. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.", "LicenseRenewalPrice": "License renewal price", "LicensePrice": "License Price", - "TrialLicensePaymentInfoSection_Description": "Purchase license: By purchasing a license, you will continue to get premium support. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.
See the license comparison table to check the differences between the license types.", + "TrialLicensePaymentInfoSection_Description": "Purchase license: By purchasing a license, you will continue to get premium support. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.
See the license comparison table to check the differences between the license types.", "SelectTargetLicense": "Select Target License", "UpgradePaymentInfoSection_ExtendMyLicenseForOneYear": "Yes, extend my license expiration date for 1 year.", "UpgradePaymentInfoSection_WantToExtendLicense": "Do you want to extend your license for 1 more year?", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 08e1687357..9f9dd49e65 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -535,7 +535,7 @@ "WhenShouldIRenewMyLicenseExplanation3": "{0} for Business and Enterprise Licenses;", "WhenShouldIRenewMyLicenseExplanation4": "However, if you renew your license more than {0} days after the expiry date, the renewal price will be the same as the initial purchase price of the license, with no discounts applied to your renewal.", "DoesTheSubscriptionRenewAutomaticallyExplanationAutoRenewal": "ABP Platform allows you to auto-renew your license. This is an optional free service. You can toggle this feature when you purchase a new license or later enable it from your organization management page. If you want to turn on or off the auto-renewal, visit the organization management page, go to the 'Payments Method' section and either check or uncheck the 'Automatic Renewal' checkbox. When you turn off the auto-renewal feature, it will be your responsibility to renew your license manually.", - "TrialPlanExplanation": "Yes, to start your free trial, please contact marketing@volosoft.com. We also offer a 30-day money-back guarantee for the Team license, with no questions asked! You can request a full refund within the first 30 days of purchasing the license. For Business and Enterprise licenses, we provide a 60% refund if requested within 30 days of purchase. This policy is due to the inclusion of the full source code for all modules and themes in the Business and Enterprise licenses.", + "TrialPlanExplanation": "Yes, to start your free trial, please contact sales@volosoft.com. We also offer a 30-day money-back guarantee for the Team license, with no questions asked! You can request a full refund within the first 30 days of purchasing the license. For Business and Enterprise licenses, we provide a 60% refund if requested within 30 days of purchase. This policy is due to the inclusion of the full source code for all modules and themes in the Business and Enterprise licenses.", "BlazoriseLicenseExplanation": "We have an agreement between Volosoft and Megabit, according to which the Blazorise license is bundled with the ABP Platform’s commercial licenses. Therefore, our paid users do not need to purchase an additional Blazorise license.", "HowToUpgradeExplanation1": "When you create a new application using the ABP startup templates, all the modules and themes are used as NuGet and NPM packages. This setup allows for easy upgrades to newer versions of the packages.", "HowToUpgradeExplanation2": "In addition to the standard NuGet/NPM upgrades, ABP CLI provides an update command that automatically finds and upgrades all ABP-related packages in your solution.", @@ -586,10 +586,10 @@ "CreatePostSummaryInfo": "A short summary of the post to be shown on the post list. Maximum length: {0}", "CreatePostCoverInfo": "For creating an effective post, add a cover photo. Upload 16:9 aspect ratio pictures for the best view.
Maximum file size: 1MB.", "CreatePostCoverInfo_Title": "Add a cover image to your post.", - "CreatePostCoverInfo1": " Accepted file types : JPEG, JPG, PNG", - "CreatePostCoverInfo2": " Max file size : 1 MB", - "CreatePostCoverInfo3": " Image proportion : 16:9", - "CreatePostCoverInfo4": " Download a sample cover image ", + "CreatePostCoverInfo1": "Accepted file types : JPEG, JPG, PNG", + "CreatePostCoverInfo2": "Max file size : 1 MB", + "CreatePostCoverInfo3": "Image proportion : 16:9", + "CreatePostCoverInfo4": " Download a sample cover image ", "ThisExtensionIsNotAllowed": "This extension is not allowed.", "TheFileIsTooLarge": "The file is too large.", "GoToThePost": "Go to the Post", @@ -1348,7 +1348,7 @@ "ExtendPaymentInfoSection_Description": "By extending/renewing your license, you will continue to get premium support. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.", "LicenseRenewalPrice": "License renewal price", "LicensePrice": "License Price", - "TrialLicensePaymentInfoSection_Description": "Purchase license: By purchasing a license, you will continue to get premium support. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.
See the license comparison table to check the differences between the license types.", + "TrialLicensePaymentInfoSection_Description": "Purchase license: By purchasing a license, you will continue to get premium support. You will also be able to get major or minor updates for modules and themes. You will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.
See the license comparison table to check the differences between the license types.", "SelectTargetLicense": "Select Target License", "UpgradePaymentInfoSection_ExtendMyLicenseForOneYear": "Yes, extend my license expiration date for 1 year.", "UpgradePaymentInfoSection_WantToExtendLicense": "Do you want to extend your license for 1 more year?", @@ -1868,6 +1868,10 @@ "GenerateQuote" : "Generate Quote" , "GeneratePriceQuote": "Generate a Price Quote", "Qa:QuestionPageTitle": "Support", - "SelectedTrainingName" : "Trainings" + "SelectedTrainingName" : "Trainings", + "RcStableDifference": "What is the difference between the RC version and the stable version of ABP?", + "RcStableDifferenceExplanation1": "The RC (Release Candidate) version is a pre-release version that allows early access to upcoming features and updates in the ABP project. It is primarily intended for testing purposes and for developers who want to prepare for the upcoming stable release. While it undergoes internal testing, it may still contain unresolved issues and it is not recommended for use in the production environment.", + "RcStableDifferenceExplanation2": "The Stable version is tested and officially supported for production use. It ensures reliability and compatibility.", + "RcStableDifferenceExplanation3": "Use the RC version for testing and early adoption but use the Stable version for production deployment." } } diff --git a/build/common.ps1 b/build/common.ps1 index 85f18edb21..51ab56d972 100644 --- a/build/common.ps1 +++ b/build/common.ps1 @@ -34,11 +34,13 @@ if ($full -eq "-f") "../templates/module/aspnet-core", "../templates/app/aspnet-core", "../templates/console", - "../templates/wpf", "../templates/app-nolayers/aspnet-core", "../abp_io/AbpIoLocalization", "../source-code" - ) + ) + if ($env:OS -eq "Windows_NT") { + $solutionPaths += "../templates/wpf" + } }else{ Write-host "" Write-host ":::::::::::::: !!! You are in development mode !!! ::::::::::::::" -ForegroundColor red -BackgroundColor yellow diff --git a/common.props b/common.props index 9b30b8d788..b1355eb36a 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 9.1.0-rc.2 - 4.1.0-rc.2 + 9.2.0-preview + 4.2.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ 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 new file mode 100644 index 0000000000..bb8bfdf4c7 --- /dev/null +++ b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/POST.md @@ -0,0 +1,147 @@ +# ABP Platform 9.1 RC Has Been Released + +We are happy to release [ABP](https://abp.io) version **9.1 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. + +Try this version and provide feedback for a more stable version of ABP v9.1! Thanks to you in advance. + +## Get Started with the 9.1 RC + +You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli). + +By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI: + +![studio-switch-to-preview.png](studio-switch-to-preview.png) + +## Migration Guide + +There are no breaking changes in this version that would affect your application. Only you might need to update some constant names due to the OpenIddict 6.0 upgrade, which is explained in the [OpenIddict 6.0 migration guide](https://abp.io/docs/9.1/release-info/migration-guides/openiddict5-to-6). + +## What's New with ABP v9.1? + +In this section, I will introduce some major features released in this version. +Here is a brief list of titles explained in the next sections: + +* Upgraded to Angular 19 +* Upgraded to OpenIddict 6.0 +* New Blazor WASM Bundling System +* Idle Session Warning +* Lazy Expandable Feature for Documentation + +### Upgraded to Angular 19 + +We've upgraded the Angular templates and packages to **Angular 19**. This upgrade brings the latest features and improvements from the Angular ecosystem to ABP-based applications, including better performance and development experience. + +### Upgraded to OpenIddict 6.0 + +OpenIddict 6.0 has been released and we've upgraded the OpenIddict packages to version 6.0 in ABP 9.1. This brings enhanced security features and improved authentication capabilities. The migration is straightforward and mainly involves updating some constant names: + +- `OpenIddictConstants.Permissions.Endpoints.Logout` is now `OpenIddictConstants.Permissions.Endpoints.EndSession` +- `OpenIddictConstants.Permissions.Endpoints.Device` is now `OpenIddictConstants.Permissions.Endpoints.DeviceAuthorization` + +If you're using IdentityModel packages directly, you'll need to upgrade them to the latest stable version (8.3.0). This update ensures your applications stay current with the latest security standards and best practices. + +> Please refer to the [OpenIddict 6.0 migration guide](https://abp.io/docs/9.1/release-info/migration-guides/openiddict5-to-6) for more information. + +### New Blazor WASM Bundling System + +We've implemented a new bundling system for Blazor WebAssembly applications that eliminates the need to manually run the `abp bundle` command. This system automatically handles JavaScript and CSS file bundling at runtime, significantly improving both development experience and application loading performance. + +**Key improvements include:** + +- Automatic bundling of JavaScript and CSS files without manual intervention +- Dynamic file generation through the host application +- Better integration with the ABP module system +- Improved asset management through virtual file system + +The new system is particularly beneficial for modular applications, as it allows modules to contribute their assets automatically to the global bundles. This results in a more maintainable and efficient asset management system for Blazor WebAssembly applications. + +> Please refer to [this documentation](https://abp.io/docs/9.1/framework/ui/blazor/global-scripts-styles) for more information. + +### Idle Session Warning + +We've introduced a new idle session warning feature for the [Account (Pro) Module](https://abp.io/docs/latest/modules/account-pro) that helps manage user sessions more effectively. This security enhancement automatically monitors user activity and manages session timeouts in a user-friendly way. + +![idle-session-settings.png](idle-session-settings.png) + +The feature can be easily configured through the administration interface, where administrators can: + +- Enable/disable the idle session timeout +- Set custom timeout duration in minutes +- Configure when users should be signed out + +When a user becomes inactive for the configured duration, they'll receive a warning dialog: + +![session-expiration-warning.png](session-expiration-warning.png) + +**Key features and behaviors:** + +- Tracks real user activity (mouse movements, keyboard presses) across all tabs +- Works on a per-browser session basis - affects all tabs of the same session +- Maintains session if user is active in any tab of the application +- Provides a countdown timer before automatic sign-out +- Offers options to "Stay signed in" or "Sign out now" + +This feature significantly improves application security while maintaining a smooth user experience by preventing unexpected session expirations and data loss. + +### Lazy Expandable Feature for Documentation + +We've introduced a new lazy expandable feature to the documentation system that significantly improves navigation through large documentation sections. This enhancement addresses common challenges when dealing with extensive documentation hierarchies by introducing smart menu management. + +**Key benefits and features:** + +- **Cleaner Navigation:** The menu stays concise by hiding sub-items until they're needed, reducing visual clutter +- **Better Performance:** Reduces the initial load of the navigation tree by loading sub-items on demand +- **Improved Search Experience:** Makes filtering documentation items more efficient by showing only relevant top-level items +- **Context-Aware Expansion:** Automatically expands relevant sections when viewing specific documentation pages + +The feature works by marking certain documentation sections as "lazy expandable" in the navigation configuration. When users navigate to a document within a lazy expandable section, the system automatically expands the relevant menu items while keeping other sections collapsed. + +This improvement is particularly valuable for complex documentation areas like tutorials, solution templates, and extensive module documentation, where having all navigation items visible at once could be overwhelming. + +An example of lazy expandable feature from the [ABP's BookStore Tutorial](https://abp.io/docs/latest/tutorials/book-store/part-01): + +```json + { + "text": "Book Store Application", + "isLazyExpandable": true, + "path": "tutorials/book-store", + "items": [ + { + "text": "Overview", + "path": "tutorials/book-store", + "isIndex": true + }, + //other items... + ] + } +``` + +![lazy-expandable.png](lazy-expandable.png) + +### Others + +Some other highlights from this release: + +* Updated Iyzico NuGet packages to the latest version, which is used in the [ABP's Payment Module](https://abp.io/docs/latest/modules/payment#payment-module-pro). +* Removed optional _secondaryIds_ from path. See: [#21307](https://github.com/abpframework/abp/pull/21307) +* [CMS Kit Pro](https://abp.io/docs/latest/modules/cms-kit-pro): Added automatic deletion of comments when a blog post is deleted - comments are now automatically removed when their associated blog post is deleted. +* Avoiding global blocking in distributed event handlers (See [#21716](https://github.com/abpframework/abp/pull/21716)). + +## Community News + +### New ABP Community Articles + +There are exciting articles contributed by the ABP community as always. I will highlight some of them here: + +* [Integrating ABP Modules in Your ASP.NET Core Web API Project. A Step-by-Step Guide](https://abp.io/community/articles/integrating-abp-modules-in-your-asp.net-core-web-api-project.-a-stepbystep-guide-jtbyosnr) by [Sajankumar Vijayan](https://abp.io/community/members/connect) +* [ABP Framework: Background Jobs vs Background Workers](https://abp.io/community/articles/abp-framework-background-jobs-vs-background-workers-when-to-use-which-t98pzjv6) — When to Use Which? by [Alper Ebiçoğlu](https://twitter.com/alperebicoglu) +* [The new Unit Test structure in ABP application](https://abp.io/community/articles/the-new-unit-test-structure-in-abp-application-4vvvp2oy) by [Liming Ma](https://github.com/maliming) +* [How to Use OpenAI API with ABP Framework](https://abp.io/community/articles/how-to-use-openai-api-with-abp-framework-rsfvihla) by [Berkan Şaşmaz](https://github.com/berkansasmaz) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/submit) to the ABP Community. + +## Conclusion + +This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/9.1/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.1 RC and provide feedback to help us release a more stable version. + +Thanks for being a part of this community! diff --git a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/cover-image.png b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/cover-image.png new file mode 100644 index 0000000000..0575db3f27 Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/cover-image.png differ diff --git a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/idle-session-settings.png b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/idle-session-settings.png new file mode 100644 index 0000000000..7cdb5df408 Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/idle-session-settings.png differ diff --git a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/lazy-expandable.png b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/lazy-expandable.png new file mode 100644 index 0000000000..0936bcb024 Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/lazy-expandable.png differ diff --git a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/session-expiration-warning.png b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/session-expiration-warning.png new file mode 100644 index 0000000000..1c4082874d Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/session-expiration-warning.png differ diff --git a/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/studio-switch-to-preview.png b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/studio-switch-to-preview.png new file mode 100644 index 0000000000..32f6d01edb Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-21 v9_1_Preview/studio-switch-to-preview.png differ diff --git a/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/POST.md b/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/POST.md new file mode 100644 index 0000000000..611d8cb41b --- /dev/null +++ b/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/POST.md @@ -0,0 +1,46 @@ +# ABP Studio Now Supports MacOS Intel 🚀 + +We are excited to announce that [ABP Studio, our cross-platform desktop application for ABP developers](https://abp.io/studio), now supports Intel-based Mac computers! + +This addition expands our platform compatibility, ensuring that developers using Intel-powered Macs can also benefit from the powerful features of ABP Studio. + +## What is ABP Studio? + +For those who aren't familiar, [ABP Studio](https://abp.io/studio) is a powerful desktop application that makes ABP development faster and easier. It offers: + +* Easy creation of new solutions (from simple applications to microservices) +* Visual architecture management for modular-monolith and microservice solutions +* Solution exploration tools for entities, services, and HTTP APIs +* Simplified running, debugging and monitoring of multi-application or microservice solutions +* Kubernetes cluster integration capabilities +* and more... + +## Extended Platform Support + +ABP Studio has been proudly supporting multiple platforms, and we're excited to add MacOS Intel to the our list of supported architectures. You can now use ABP Studio on: + +* Windows x64 +* Windows ARM +* MacOS Apple Silicon (M1/M2/M3) +* MacOS Intel **(New!)** + +## Why This Matters + +This update is particularly important for developers who are using Intel-based Mac computers. Previously, ABP Studio was only available for Apple Silicon Macs (for MacOS), but we understand that many developers are still using Intel-based Macs. With this release, we're ensuring that all Mac users can access our development tools, regardless of their processor architecture. + +## Getting Started + +Installing ABP Studio on your Intel-based Mac is straightforward: + +1. Go to [abp.io/studio](https://abp.io/studio) +2. Click on the download button and select "MacOS Intel" from the dropdown menu +3. Once downloaded, open the installer package +4. Follow the installation wizard to complete the setup + +![ABP Studio Download Page](abp-studio-macos-intel.png) + +## Conclusion + +As ABP team, we're always looking for ways to improve the developer experience. By supporting Intel-based Macs, we're ensuring that all Mac users can access our development tools, regardless of their processor architecture. + +Stay tuned for more updates and enhancements as we continue to optimize ABP Studio and please provide us with your invaluable feedback. Thanks in advance! diff --git a/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/abp-studio-macos-intel.png b/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/abp-studio-macos-intel.png new file mode 100644 index 0000000000..f5d5ca6987 Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/abp-studio-macos-intel.png differ diff --git a/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/cover-image.png b/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/cover-image.png new file mode 100644 index 0000000000..80e0d0faf0 Binary files /dev/null and b/docs/en/Blog-Posts/2025-01-22 ABP_Now_Supports_MacOS_Intel/cover-image.png differ diff --git a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md index c1c38d7818..5c1b03701e 100644 --- a/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md +++ b/docs/en/Community-Articles/2023-11-06-Blazor-Fullstack-Web-Ui/Post.md @@ -1,8 +1,5 @@ # Blazor's History and Full-stack Web UI -![Cover Image](cover-image.png) - - Blazor is a web framework that allows developers to build interactive web applications using .NET instead of JavaScript. The first version of Blazor was released on May 14, 2020. Since its initial release, Blazor has evolved with the new versions. Until now, six different versions have been declared. Sometimes, it can be not very clear to see the differences between these approaches. First, let's try to understand these. * **Blazor-Server**: >> *Loads fast at first* >> In this version, heavy things are done in the server. Browsers are thin clients and download a small page for the first load. The page updates are done via SignalR connection. This was released with .NET Core 3. diff --git a/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md b/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md index a45f893b16..c6a37d85cd 100644 --- a/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md +++ b/docs/en/Community-Articles/2024-01-15-Abp-Supports-NET8/Post.md @@ -1,5 +1,3 @@ -![cover](cover.png) - # ABP Now Supports .NET 8 Recently we have published ABP v8.0. With this version [the ABP Framework](https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj#L6) and ABP Commercial both supports for .NET 8, aligning itself with the latest enhancements and new features of the ASP.NET's new version 8. @@ -170,4 +168,4 @@ Starting in .NET 8, C# Hot Reload [supports modifying generic types and generic *References:* -* https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8 \ No newline at end of file +* https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8 diff --git a/docs/en/Community-Articles/2024-08-28-Understanding-AOT-vs-JIT/POST.md b/docs/en/Community-Articles/2024-08-28-Understanding-AOT-vs-JIT/POST.md index 295b509029..cd4c3177a1 100644 --- a/docs/en/Community-Articles/2024-08-28-Understanding-AOT-vs-JIT/POST.md +++ b/docs/en/Community-Articles/2024-08-28-Understanding-AOT-vs-JIT/POST.md @@ -1,7 +1,3 @@ - - -![book](images/cover.png) - Ahead-of-Time (AOT) compilation and Just-in-Time (JIT) compilation are two different methods for compiling Angular applications. Here's a breakdown of the differences between them: ### **Ahead-of-Time (AOT) Compilation** @@ -61,4 +57,4 @@ Ahead-of-Time (AOT) compilation and Just-in-Time (JIT) compilation are two diffe ### **Best Practices:** - **Use AOT** for production builds to ensure faster load times, smaller bundle sizes, and more secure applications. -- **Use JIT** during development to take advantage of quicker builds and easier debugging. \ No newline at end of file +- **Use JIT** during development to take advantage of quicker builds and easier debugging. diff --git a/docs/en/Community-Articles/2024-08-29-What-is-Angular-Schematics/Post.md b/docs/en/Community-Articles/2024-08-29-What-is-Angular-Schematics/Post.md index c13c5ab345..5ea6cb6528 100644 --- a/docs/en/Community-Articles/2024-08-29-What-is-Angular-Schematics/Post.md +++ b/docs/en/Community-Articles/2024-08-29-What-is-Angular-Schematics/Post.md @@ -1,7 +1,5 @@ # What is Angular Schematics? -![Cover Image](cover.png) - **Angular Schematics** is a powerful tool which is part of the Angular CLI that allows developers to automate various development tasks by **generating and modifying code**. Schematics provides a way to create **templates and boilerplate code** for Angular applications or libraries, enabling consistency and reducing the amount of repetitive work. ### Key Concepts of Angular Schematics: @@ -82,4 +80,4 @@ Here are the direct links for the Angular Schematics resources: ### Conclusion: -Angular Schematics is a powerful tool for automating repetitive tasks, generating consistent code, and managing project upgrades. By leveraging schematics, Angular developers can save time, reduce errors, and enforce best practices across their projects. \ No newline at end of file +Angular Schematics is a powerful tool for automating repetitive tasks, generating consistent code, and managing project upgrades. By leveraging schematics, Angular developers can save time, reduce errors, and enforce best practices across their projects. diff --git a/docs/en/Community-Articles/2024-09-01-Do-You-Need-MultiTenancy/Post.md b/docs/en/Community-Articles/2024-09-01-Do-You-Need-MultiTenancy/Post.md index b80917f4c4..a294a1f2e6 100644 --- a/docs/en/Community-Articles/2024-09-01-Do-You-Need-MultiTenancy/Post.md +++ b/docs/en/Community-Articles/2024-09-01-Do-You-Need-MultiTenancy/Post.md @@ -2,8 +2,6 @@ This article discusses whether you need a multi-tenancy architecture for your next project. Answer my critical questions to decide if multi-tenancy suits your application or not! -![Cover image](cover.png) - ## What’s Multi-tenancy? It’s an architectural approach to building SaaS solutions. In this model, the hardware and software resources are shared between tenants, and application data is virtually or physically isolated between tenants. Here, **the main goal is minimizing costs and maximizing customer count**. diff --git a/docs/en/Community-Articles/2024-09-18-Blazor-9-New-Features/post.md b/docs/en/Community-Articles/2024-09-18-Blazor-9-New-Features/post.md index bc73c13210..1d78b493e4 100644 --- a/docs/en/Community-Articles/2024-09-18-Blazor-9-New-Features/post.md +++ b/docs/en/Community-Articles/2024-09-18-Blazor-9-New-Features/post.md @@ -2,8 +2,6 @@ In this article, I'll highlight .NET 9's Blazor updates and important features for ASP.NET Core 9.0. These features are based on the latest .NET 9 Preview 7. -![Cover](cover.png) - ## .NET MAUI Blazor Hybrid App and Web App solution template There's a new solution template to create .**NET MAUI native** and **Blazor web client** apps. This new template allows to choose a Blazor interactive render mode, it uses a shared Razor class library to maintain the UI's Razor components. diff --git a/docs/en/Community-Articles/2024-09-24-Angular-Difference-Btw-Promise-Observable/post.md b/docs/en/Community-Articles/2024-09-24-Angular-Difference-Btw-Promise-Observable/post.md index 39d722a293..9f8ae4c9d7 100644 --- a/docs/en/Community-Articles/2024-09-24-Angular-Difference-Btw-Promise-Observable/post.md +++ b/docs/en/Community-Articles/2024-09-24-Angular-Difference-Btw-Promise-Observable/post.md @@ -2,10 +2,6 @@ In this article, I will mention the differences between `Promise` and `Observable` . They are used in TypeScript (Angular) for handling async operations but have different use cases and behaviors. Let's see these six differences... -![Cover](cover.png) - - - ## 1. Eager or Lazy Evaluation - **Promise**: A `promise` is **eager**! This means that as soon as a `promise` is created, it executes the operation, like initiating immediately an HTTP request. **You can't control the execution start time; it begins right away!** diff --git a/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md index c004d45eba..c92e38f764 100644 --- a/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md +++ b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md @@ -1,11 +1,6 @@ ### ASP.NET Core SignalR New Features — Summary - In this article, I’ll highlight the latest .**NET 9 SignalR updates** for ASP.NET Core 9.0. -![Cover](cover.png) - - - ### SignalR Hub Accepts Base Classes SignalR `Hub` class can now get a base class of a polymorphic class. As you see in the example below, I can send `Animal` to `Process` method. Before .NET 9, we could only pass the derived classes: `Cat` and `Dog`. diff --git a/docs/en/Community-Articles/2024-10-09-Cookies-vs-Local-Storage/Post.md b/docs/en/Community-Articles/2024-10-09-Cookies-vs-Local-Storage/Post.md index bac7f69596..b7122253ab 100644 --- a/docs/en/Community-Articles/2024-10-09-Cookies-vs-Local-Storage/Post.md +++ b/docs/en/Community-Articles/2024-10-09-Cookies-vs-Local-Storage/Post.md @@ -1,9 +1,5 @@ # When to Use Cookies, When to Use Local Storage? -![cover](cover.png) - - - ## Cookies vs Local Storage When you want to save client-side data on browsers, you can use `Cookies` or `Local Storage` of the browser. While these methods look similar, they have different behaviors. You need to decide based on the specific use-case, security concerns and the data size being stored. I'll clarify the differences between these methods. @@ -60,4 +56,4 @@ When you want to save client-side data on browsers, you can use `Cookies` or `Lo In many cases, you might use both cookies and local storage, depending on the specific requirements of different parts of your application. There are also other places where you can store the client-side data. You can check out [this article](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage) for more information. -Happy coding 🧑🏽‍💻 \ No newline at end of file +Happy coding 🧑🏽‍💻 diff --git a/docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/Post.md b/docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/Post.md index 67e451f0df..4710cc0a99 100644 --- a/docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/Post.md +++ b/docs/en/Community-Articles/2024-10-09-NET9-Performance-Improvements/Post.md @@ -2,8 +2,6 @@ With every release, .NET becomes faster & faster! You get these improvements for free by just updating your project to the latest .NET! -![Cover Image](cover.png) - It’s very interesting that **20% of these improvements** are implemented by **open-source volunteers** rather than Microsoft employees. These improvements mostly focus on cloud-native and high-throughput applications. I’ll briefly list them below. ![From Microsoft Blog Post](cited-from-microsoft-blog-post.png) diff --git a/docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/POST.md b/docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/POST.md index 9e996d2099..fe7b706b5a 100644 --- a/docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/POST.md +++ b/docs/en/Community-Articles/2024-10-11-NET-Aspire-vs-ABP-Studio/POST.md @@ -2,8 +2,6 @@ In this article, I will compare [.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/) by [ABP Studio](https://abp.io/docs/latest/studio) by explaining their similarities and differences. -![cover](cover.png) - ## Introduction While .NET Aspire and ABP Studio are tools for different purpose with different scope and they have different approaches to solve the problems, many developers still may confuse since they also have some similar functionalities and solves some common problems. diff --git a/docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/post.md b/docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/post.md index 2a75a97df3..bc8bd691b5 100644 --- a/docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/post.md +++ b/docs/en/Community-Articles/2024-10-23-Abp-Net9-Upgrade/post.md @@ -1,9 +1,5 @@ # ABP Now Supports .NET 9 -![Cover image](cover.png) - - - **.NET 9.0.100-rc.2** has been released on **October 8, 2024**. To align with the latest .NET, we also released the ABP Platform [9.0.0-rc.1](https://github.com/abpframework/abp/releases/tag/9.0.0-rc.1) version. **With this release, ABP now supports .NET 9.** diff --git a/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/post.md b/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/post.md index 3e950c38c0..f257577ac2 100644 --- a/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/post.md +++ b/docs/en/Community-Articles/2024-11-13-BuiltIn-OpenApi-Documentation/post.md @@ -1,8 +1,6 @@ Built-in OpenAPI Document Generation with .NET 9 — No more SwaggerUI! 👋 ======================================================================== -![Cover](cover.png) - What’s Swagger UI? ------------------ diff --git a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md index c07509d2e3..830f0d094a 100644 --- a/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md +++ b/docs/en/Community-Articles/2024-12-01-OpenAI-Integration/POST.md @@ -2,8 +2,6 @@ In this article, I will show you how to integrate and use the [OpenAI API](https://github.com/openai/openai-dotnet?tab=readme-ov-file#getting-started) with the [ABP Framework](https://abp.io/). We will explore step-by-step how these technologies can work together to enhance your application with powerful AI capabilities, such as natural language processing, image generation, and more. -![cover-image](cover-image.png) - ## Creating an ABP Project To begin integrating OpenAI API with ABP Framework, you first need to create an ABP project. Follow these steps to create and set up your ABP project: @@ -62,6 +60,8 @@ dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease > Replace the value of the `Key` with your OpenAI API key. +> **Important Security Note**: Storing sensitive information like API keys in `appsettings.json` is not recommended due to security concerns. Please refer to the [official Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) for secure secret management best practices. + Next, add the following code to the `ConfigureServices` method in `OpenAIIntegrationBlazorModule`: ```csharp @@ -382,4 +382,4 @@ To showcase the integration of the OpenAI API with the ABP Framework, we impleme ## Conclusion -In this article, we covered how to integrate the OpenAI API with the ABP Framework by creating a sample project, setting up the OpenAI services, and implementing examples for conversational AI, knowledge-based assistance, and image generation. By following these steps, you can add powerful AI-driven capabilities to your application, making it more interactive, intelligent, and capable of meeting user needs effectively. \ No newline at end of file +In this article, we covered how to integrate the OpenAI API with the ABP Framework by creating a sample project, setting up the OpenAI services, and implementing examples for conversational AI, knowledge-based assistance, and image generation. By following these steps, you can add powerful AI-driven capabilities to your application, making it more interactive, intelligent, and capable of meeting user needs effectively. diff --git a/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/background-jobs-vs-background-workers.md b/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/background-jobs-vs-background-workers.md new file mode 100644 index 0000000000..43a3c3a813 --- /dev/null +++ b/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/background-jobs-vs-background-workers.md @@ -0,0 +1,407 @@ +# ABP Framework: Background Jobs vs Background Workers — When to Use Which? + +In the ABP Framework, **Background Jobs** and **Background Workers** serve different purposes but can sometimes seem interchangeable. Sometimes it can be confusing. Let’s clarify their differences, and I'll show you some real-world cases to help you understand how to decide between these two. We have official documents for these: + +📕 **Background Workers ABP Document** https://abp.io/docs/latest/framework/infrastructure/background-workers + +📘 **Background Jobs ABP Document** https://abp.io/docs/latest/framework/infrastructure/background-jobs + + +--- + +I posted this article because recently, I came across [this support ticket](https://abp.io/support/questions/5931/Background-Jobs-vs-Background-Workers-when-to-use-which) on the ABP support website. I thought these terms could be confusing for devs. He has two tasks and is asking whether he needs to use Background Worker or Background Job. + +* The first one is `FileRecievedActivity.` He says it's running 2 times a day. So, it's a recurring activity that needs to run in the background. So `FileRecievedActivity` is a "Background Worker". +* The second one is `ProcessQueueFiles`. It sounds like a polling task. It runs whenever a new file comes to the directory. So, it's also a recurring task that needs to be scheduled every 1 minute. Hence the `ProcessQueueFiles` is also "Backgrounder Worker." + +![https://abp.io/support/questions/5931/Background-Jobs-vs-Background-Workers-when-to-use-which](support-question.png) + + + +## Background Workers 🔄 */Looping/* + +The background workers are stateless and runs in-memory as long as the application is up and ready. + +- **Purpose**: Long-running, periodic tasks that run continuously in the background. They are queued and executed asynchronously. +- **Lifetime:** Runs throughout the application's lifetime. +- **Scheduling:** Executes on a fixed schedule, like every X minutes / hours... +- **Infrastructure**: ABP uses an in-memory implementation for running background workers. You can also use the 3rd party tools for running your background worker: + * [Quartz + ABP integration](https://abp.io/docs/latest/framework/infrastructure/background-workers/quartz) + * [Hangfire + ABP integration](https://abp.io/docs/latest/framework/infrastructure/background-workers/hangfire) + + + +**Use Cases:** Use background workers for any task that needs to run repeatedly at fixed intervals. For example "Health checks", "Periodic cleanup tasks", "Monitoring tasks", "Processing daily data"... In the last section, you will find real-world examples. + + + +> In Microsoft Docs, this topic is called "Background Tasks". Check out [Microsoft's official doc for running background tasks](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services). + + + +--- + + + +## Background Jobs ▶ */One time/* + +* **Purpose:** One-time tasks that need to be queued and can be executed in the background. Here the main goal is not to block the application to run the task. + +- **Lifetime**: Executes one time and completes. + +- **Scheduling**: Can be delayed/scheduled for a specific time. + +- **Infrastructure**: ABP uses in-memory implementation but you can also use the below 3rd party tools: + + * [Hangfire + ABP implementation](https://abp.io/docs/latest/framework/infrastructure/background-jobs/hangfire) + + * [RabbitMQ + ABP implementation](https://abp.io/docs/latest/framework/infrastructure/background-jobs/rabbitmq) + + * [Quartz + ABP implementation](https://abp.io/docs/latest/framework/infrastructure/background-jobs/quartz) + + + +**Use Cases:** Use background jobs to send emails, process uploaded files, generating reports, any fire-and-forget tasks or tasks that need retry mechanisms. + + + +> In Microsoft docs this topic is called as "Background Tasks". Check out [Microsoft's official background jobs doc](https://learn.microsoft.com/en-us/azure/architecture/best-practices/background-jobs). + + + +## When to Use Which? + +### Use Background Workers when: + +- You need continuous, recurring execution + +- The task should run as long as the application is running + +- You don't need persistence in task state + +- You want in-memory, efficient execution + + + +### Use Background Jobs when: + +- You need one-time execution + +- The task should survive application restarts + +- You need guaranteed execution + +- You want built-in retry mechanisms + +- You need to queue multiple instances of the same task + + + +## Technical Differences + +```csharp +// A Background Worker Example +public class MyBackgroundWorker : AsyncPeriodicBackgroundWorkerBase +{ + public MyBackgroundWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) + : base(timer, serviceScopeFactory) + { + Timer.Period = 5000; // Runs every 5 seconds + } +} + +// A Background Job Example +public class MyBackgroundJob : AsyncBackgroundJob +{ + public override async Task ExecuteAsync(MyArgs args) + { + // Executes once when triggered + } +} + +// Triggering a Background Job +await _backgroundJobManager.EnqueueAsync( + new MyArgs { /* ... */ }, + delay: TimeSpan.FromHours(2) +); +``` + + + + +> While background workers no additional infrastructure needed, background jobs requires a database to persist jobs (or a distributed cache/message broker depending on the implementation) + + + + + +## 🚩 Background Worker Examples + + + +### 1) Querying an external API + +- **Scenario**: Continuously fetch data from an external API at regular intervals. +- **Why a Background Worker?**: You need a long-running process to poll the API and handle the results. + +```csharp +public class ApiPollingWorker : AsyncPeriodicBackgroundWorkerBase +{ + private readonly IAmazonPriceService _apiService; + + public ApiPollingWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory, IAmazonPriceService apiService) + : base(timer, serviceScopeFactory) + { + _apiService = apiService; + Timer.Period = 60000; // Run every 1 minute + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + var data = await _apiService.FetchPricesAsync(); + // Process the data + } +} +``` + +Here are practical examples of **Background Workers** that demonstrate how to use them effectively within the ABP Framework. Background Workers are ideal for long-running or periodic tasks that require stateful or persistent execution. + + + +### 2) Polling an External API + +- **Scenario**: Continuously fetch data from an external API at regular intervals. +- **Why a Background Worker?**: You need a long-running process to poll the API and handle the results. + +``` +csharpCopy codepublic class ApiPollingWorker : AsyncPeriodicBackgroundWorkerBase +{ + private readonly IApiService _apiService; + + public ApiPollingWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory, IApiService apiService) + : base(timer, serviceScopeFactory) + { + _apiService = apiService; + Timer.Period = 60000; // Run every 1 minute + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + var data = await _apiService.FetchDataAsync(); + // Process the data + } +} +``` + + + +### 3) Processing a Queue + +- **Scenario**: Continuously process items from a queue, such as Azure Service Bus or RabbitMQ. +- **Why a Background Worker?**: This requires a stateful, persistent process to monitor and handle the queue. + +```csharp +public class QueueProcessingWorker : AsyncBackgroundWorker +{ + private readonly IQueueService _queueService; + + public QueueProcessingWorker(IServiceScopeFactory serviceScopeFactory, IQueueService queueService) + : base(serviceScopeFactory) + { + _queueService = queueService; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var message = await _queueService.GetNextMessageAsync(); + if (message != null) + { + await _queueService.ProcessMessageAsync(message); + } + await Task.Delay(1000, stoppingToken); // Delay for throttling + } + } +} +``` + + + +### 4) Maintaining a Database + +- **Scenario**: Perform periodic database maintenance, rebuild indices or cleanup tasks. +- **Why a Background Worker?**: These are recurring tasks requiring regular execution. + +```csharp +public class DatabaseCleanupWorker : AsyncPeriodicBackgroundWorkerBase +{ + private readonly ICleanupService _cleanupService; + + public DatabaseCleanupWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory, ICleanupService cleanupService) + : base(timer, serviceScopeFactory) + { + _cleanupService = cleanupService; + Timer.Period = 3600000; // Run every hour + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + await _cleanupService.DeleteOldRecordsAsync(); + } +} +``` + + + +### 5) Running a Health Check Service + +- **Scenario**: Periodically check the health of connected services and log their status. +- **Why a Background Worker?**: Periodic health checks are naturally suited to background workers. + +```csharp +public class HealthCheckWorker : AsyncPeriodicBackgroundWorkerBase +{ + private readonly IHealthCheckService _healthCheckService; + + public HealthCheckWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory, IHealthCheckService healthCheckService) + : base(timer, serviceScopeFactory) + { + _healthCheckService = healthCheckService; + Timer.Period = 60000; // Run every minute + } + + protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + await _healthCheckService.PerformChecksAsync(); + } +} +``` + + + +--- + + + +## 🚩 Background Job Examples + + + +### 1) Email Notifications + +- **Scenario**: Sending a welcome email to new users. +- **Why a Background Job?**: The email sending process doesn’t need to block the main application thread. It can run in the background, and failed jobs can be retried. + +```csharp +public class SendWelcomeEmailJob : IBackgroundJob +{ + private readonly IEmailSender _emailSender; + + public SendWelcomeEmailJob(IEmailSender emailSender) + { + _emailSender = emailSender; + } + + public async Task ExecuteAsync(string emailAddress) + { + await _emailSender.SendAsync( + emailAddress, + "Welcome!", + "Thank you for signing up to our service." + ); + } +} +``` + + + +### 2) Data Import/Export + +- **Scenario**: Exporting a large dataset to a CSV file and notifying the user once complete. +- **Why a Background Job?**: Long-running tasks like file generation can be processed asynchronously. + +```csharp +public class ExportDataJob : IBackgroundJob +{ + private readonly IDataExporter _dataExporter; + private readonly INotificationService _notificationService; + + public ExportDataJob(IDataExporter dataExporter, INotificationService notificationService) + { + _dataExporter = dataExporter; + _notificationService = notificationService; + } + + public async Task ExecuteAsync(int userId) + { + var filePath = await _dataExporter.ExportAsync(userId); + await _notificationService.NotifyAsync(userId, "Your data export is complete: " + filePath); + } +} +``` + + + +### 3) Push Notifications + +- **Scenario**: Sending push notifications to users about specific events (e.g., “Your order has been shipped!”). +- **Why a Background Job?**: Push notifications are non-blocking and can be handled in bulk in the background. + +```csharp +public class SendPushNotificationJob : IBackgroundJob +{ + private readonly IPushNotificationService _pushNotificationService; + + public SendPushNotificationJob(IPushNotificationService pushNotificationService) + { + _pushNotificationService = pushNotificationService; + } + + public async Task ExecuteAsync(PushNotificationInput input) + { + await _pushNotificationService.SendAsync(input.UserId, input.Message); + } +} +``` + + + +### 4) File Upload Processing + +- **Scenario**: Processing a file uploaded by a user (e.g., parsing, validating, and saving to a database). +- **Why a Background Job?**: File processing can be offloaded to the background, ensuring quick user feedback. + +```csharp +public class ProcessFileUploadJob : IBackgroundJob +{ + private readonly IFileParser _fileParser; + private readonly IDataService _dataService; + + public ProcessFileUploadJob(IFileParser fileParser, IDataService dataService) + { + _fileParser = fileParser; + _dataService = dataService; + } + + public async Task ExecuteAsync(FileProcessingInput input) + { + var parsedData = await _fileParser.ParseAsync(input.FilePath); + await _dataService.SaveAsync(parsedData); + } +} +``` + + + +## Final Words + +Use **Background Workers** for stateful, continuous operations and **Background Jobs** for isolated, retriable units of work. + +I hope this article will clarify these terms in ABP Framework. + +https://abp.io/ a complete web application platform. + +Happy coding😊 + + diff --git a/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/cover.png b/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/cover.png new file mode 100644 index 0000000000..ce7ebd6b15 Binary files /dev/null and b/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/cover.png differ diff --git a/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/support-question.png b/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/support-question.png new file mode 100644 index 0000000000..39ac71d8e1 Binary files /dev/null and b/docs/en/Community-Articles/2025-01-03-ABP-Background-Workers-vs-Jobs/support-question.png differ diff --git a/docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/Load-User-Profile.jpg b/docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/Load-User-Profile.jpg new file mode 100644 index 0000000000..85f736050f Binary files /dev/null and b/docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/Load-User-Profile.jpg differ diff --git a/docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/POST.md b/docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/POST.md new file mode 100644 index 0000000000..cea4bb82eb --- /dev/null +++ b/docs/en/Community-Articles/2025-01-23-Fixing-OpenIddict-Certificate-Issues/POST.md @@ -0,0 +1,77 @@ +# Fixing OpenIddict Certificate Issues in IIS or Azure + +When deploying an ABP application with OpenIddict to IIS or Azure, you may encounter issues with loading PFX/PKCS12 certificates. This article explains how to properly configure certificate loading to ensure it works correctly in these environments. + +## The Problem + +When running under IIS or Azure, the application pool identity may not have sufficient permissions to access certificate private keys. This commonly results in errors such as: + +- `System.Security.Cryptography.CryptographicException: Access denied.` +- `WindowsCryptographicException: Access is denied.` +- `System.Security.Cryptography.CryptographicException: The system cannot find the file specified.` + +## The Solution + +### Using AddDevelopmentEncryptionAndSigningCertificate + +For development environments using `DevelopmentEncryptionAndSigningCertificate`, you must configure the application pool to load a user profile. + +> Note: We strongly recommend using `DevelopmentEncryptionAndSigningCertificate` only in development environments. For production, always create and use a separate certificate. + +![Application Pool Configuration](Load-User-Profile.jpg) + +### Using AddProductionEncryptionAndSigningCertificate + +The ABP OpenIddict module provides an `AddProductionEncryptionAndSigningCertificate` extension method. By default, the template project attempts to load an `openiddict.pfx` certificate in production environments. + +To ensure proper certificate loading in IIS or Azure, you need to specify appropriate `X509KeyStorageFlags` when calling this method: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + if (!hostingEnvironment.IsDevelopment()) + { + PreConfigure(options => + { + options.AddDevelopmentEncryptionAndSigningCertificate = false; + }); + + PreConfigure(serverBuilder => + { + var flag = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet; + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", "YourCertificatePassword", flag); + }); + } +} +``` + +## Understanding X509KeyStorageFlags + +The configuration uses two important flags: + +* `X509KeyStorageFlags.MachineKeySet`: Specifies that the key belongs to the local computer key store, binding the key pair's lifecycle to the computer rather than a specific user. +* `X509KeyStorageFlags.EphemeralKeySet`: Indicates that the key will be stored only in memory and not persisted to disk or key store, enhancing security for runtime-only certificate requirements. + +Using these flags in combination helps prevent permission-related issues in IIS and Azure environments. + +## Troubleshooting Guide + +If you continue to experience issues, verify the following: + +* Confirm that the certificate password is correct +* Verify that the `openiddict.pfx` file exists in your deployment +* Ensure the certificate is valid - you can generate a new one using: + ```bash + dotnet dev-certs https -v -ep openiddict.pfx -p YourCertificatePassword + ``` +* Check the stdout logs for related errors (See [how to get stdout-log](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/deployment-iis?UI=Blazor&DB=EF&Tiered=No#how-to-get-stdout-log)) + +## References + +- [ABP OpenIddict Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/openiddict-deployment) +- [ABP IIS Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/deployment-iis) +- [ABP Azure Deployment](https://abp.io/docs/latest/solution-templates/layered-web-application/deployment/azure-deployment/azure-deployment) +- [How to Generate a New Certificate](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs#how-to-generate-a-new-certificate) +- [Load User Profile in IIS](https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#load-user-profile-for-an-application-pool) diff --git a/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/POST.md b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/POST.md new file mode 100644 index 0000000000..929203c6a0 --- /dev/null +++ b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/POST.md @@ -0,0 +1,325 @@ +# Understanding Transactions in ABP Unit of Work + +[The Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) is a software design pattern that maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems to ensure that all changes are made within a single transaction. + +## Transaction Management Overview + +One of the primary responsibilities of the Unit of Work is managing database transactions. It provides the following transaction management features: + +- Automatically manages database connections and transaction scopes, eliminating the need for manual transaction control +- Ensures business operation integrity by making all database operations within a unit of work either succeed or roll back completely +- Supports configuration of transaction isolation levels and timeout periods +- Supports nested transactions and transaction propagation + +## Transaction Behavior + +### Default Transaction Settings + +You can modify the default behavior through the following configuration: + +```csharp +Configure(options => +{ + /* + Modify the default transaction behavior for all unit of work: + - UnitOfWorkTransactionBehavior.Enabled: Always enable transactions, all requests will start a transaction + - UnitOfWorkTransactionBehavior.Disabled: Always disable transactions, no requests will start a transaction + - UnitOfWorkTransactionBehavior.Auto: Automatically decide whether to start a transaction based on HTTP request type + */ + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + + // Set default timeout + options.Timeout = TimeSpan.FromSeconds(30); + + // Set default isolation level + options.IsolationLevel = IsolationLevel.ReadCommitted; +}); +``` + +### Automatic Transaction Management + +ABP Framework implements automatic management of Unit of Work and transactions through middlewares, MVC global filters, and interceptors. In most cases, you don't need to manage them manually + +### Transaction Behavior for HTTP Requests + +By default, the framework adopts an intelligent transaction management strategy for HTTP requests: +- `GET` requests won't start a transactional unit of work because there is no data modification +- Other HTTP requests (`POST/PUT/DELETE` etc.) will start a transactional unit of work + +### Manual Transaction Control + +If you need to manually start a new unit of work, you can customize whether to start a transaction and set the transaction isolation level and timeout: + +```csharp +// Start a transactional unit of work +using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 +)) +{ + // Execute database operations within transaction + await uow.CompleteAsync(); +} +``` + +```csharp +// Start a non-transactional unit of work +using (var uow = _unitOfWorkManager.Begin( + isTransactional: false +)) +{ + // Execute database operations without transaction + await uow.CompleteAsync(); +} +``` + +### Configuring Transactions Using `[UnitOfWork]` Attribute + +You can customize transaction behavior by using the `UnitOfWorkAttribute` on methods, classes, or interfaces: + +```csharp +[UnitOfWork( + IsTransactional = true, + IsolationLevel = IsolationLevel.RepeatableRead, + Timeout = 30 +)] +public virtual async Task ProcessOrderAsync(int orderId) +{ + // Execute database operations within transaction +} +``` + +### Non-Transactional Unit of Work + +In some scenarios, you might not need transaction support. You can create a non-transactional unit of work by setting `IsTransactional = false`: + +```csharp +public virtual async Task ImportDataAsync(List items) +{ + using (var uow = _unitOfWorkManager.Begin( + isTransactional: false + )) + { + foreach (var item in items) + { + await _repository.InsertAsync(item, autoSave: true); + // Each InsertAsync will save to database immediately + // If subsequent operations fail, saved data won't be rolled back + } + + await uow.CompleteAsync(); + } +} +``` + +Applicable scenarios: +- Batch import data scenarios where partial success is accepted +- Read-only operations, such as queries +- Scenarios with low data consistency requirements + +### Methods to Commit Transactions + +#### In Transactional Unit of Work + +A Unit of Work provides several methods to commit changes to the database: + +1. **IUnitOfWork.SaveChangesAsync** + +```csharp +await _unitOfWorkManager.Current.SaveChangesAsync(); +``` + +2. **autoSave parameter in repositories** + +```csharp +await _repository.InsertAsync(entity, autoSave: true); +``` + +Both `autoSave` and `SaveChangesAsync` commit changes in the current context to the database. However, these are not applied until `CompleteAsync` is called. If the unit of work throws an exception or `CompleteAsync` is not called, the transaction will be rolled back. It means all the DB operations will be reverted back. Only after successfully executing `CompleteAsync` will the transaction be permanently committed to the database. + +3. **CompleteAsync** + +```csharp +using (var uow = _unitOfWorkManager.Begin()) +{ + // Execute database operations + await uow.CompleteAsync(); +} +``` + +When you manually control the Unit of Work with `UnitOfWorkManager`, the `CompleteAsync` method is crucial for transaction completion. The unit of work maintains a `DbTransaction` object internally, and the `CompleteAsync` method invokes `DbTransaction.CommitAsync` to commit the transaction. The transaction will not be committed if `CompleteAsync` is either not executed or fails to execute successfully. + +This method not only commits all database transactions but also: + +- Executes and processes all pending domain events within the Unit of Work +- Executes all registered post-operations and cleanup tasks within the Unit of Work +- Releases all DbTransaction resources upon disposal of the Unit of Work object + +> Note: `CompleteAsync` method should be called only once. Multiple calls are not supported. + +#### In Non-Transactional Unit of Work + +In non-transactional Unit of Work, these methods behave differently: + +Both `autoSave` and `SaveChangesAsync` will persist changes to the database immediately, and these changes cannot be rolled back. Even in non-transactional Unit of Work, calling the `CompleteAsync` method remains necessary as it handles other essential tasks. + +Example: +```csharp +using (var uow = _unitOfWorkManager.Begin(isTransactional: false)) +{ + // Changes are persisted immediately and cannot be rolled back + await _repository.InsertAsync(entity1, autoSave: true); + + // This operation persists independently of the previous operation + await _repository.InsertAsync(entity2, autoSave: true); + + await uow.CompleteAsync(); +} +``` + +### Methods to Roll Back Transactions + +#### In Transactional Unit of Work + +A unit of work provides multiple approaches to roll back transactions: + +1. **Automatic Rollback** + +For transactions automatically managed by the ABP Framework, any uncaught exceptions during the request will trigger an automatic rollback. + +2. **Manual Rollback** + +For manually managed transactions, you can explicitly invoke the `RollbackAsync` method to immediately roll back the current transaction. + +> Important: Once `RollbackAsync` is called, the entire Unit of Work transaction will be rolled back immediately, and any subsequent calls to `CompleteAsync` will have no effect. + +```csharp +using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 +)) +{ + await _repository.InsertAsync(entity); + + if (someCondition) + { + await uow.RollbackAsync(); + return; + } + + await uow.CompleteAsync(); +} +``` + +The `CompleteAsync` method attempts to commit the transaction. If any exceptions occur during this process, the transaction will not be committed. + +Here are two common exception scenarios: + +1. **Exception Handling Within Unit of Work** + +```csharp +using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 +)) +{ + try + { + await _bookRepository.InsertAsync(book); + await uow.SaveChangesAsync(); + await _productRepository.UpdateAsync(product); + await uow.CompleteAsync(); + } + catch (Exception) + { + // Exceptions can occur in InsertAsync, SaveChangesAsync, UpdateAsync, or CompleteAsync + // Even if some operations succeed, the transaction remains uncommitted to the database + // While you can explicitly call RollbackAsync to roll back the transaction, + // the transaction will not be committed anyway if CompleteAsync fails to execute + throw; + } +} +``` + +2. **Exception Handling Outside Unit of Work** + +```csharp +try +{ + using (var uow = _unitOfWorkManager.Begin( + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 + )) + { + await _bookRepository.InsertAsync(book); + await uow.SaveChangesAsync(); + await _productRepository.UpdateAsync(product); + await uow.CompleteAsync(); + } +} +catch (Exception) +{ + // Exceptions can occur in UpdateAsync, SaveChangesAsync, UpdateAsync, or CompleteAsync + // Even if some operations succeed, the transaction remains uncommitted to the database + // Since CompleteAsync was not successfully executed, the transaction will not be committed + throw; +} +``` + +#### In Non-Transactional Unit of Work + +In non-transactional units of work, operations are irreversible. Changes saved using `autoSave: true` or `SaveChangesAsync()` are persisted immediately, and the `RollbackAsync` method has no effect. + +## Transaction Management Best Practices + +### 1. Remember to Commit Transactions + +When manually controlling transactions, remember to call the `CompleteAsync` method to commit the transaction after operations are complete. + +### 2. Pay Attention to Context + +If a unit of work already exists in the current context, `UnitOfWorkManager.Begin` method and` UnitOfWorkAttribute` will **reuse it**. Specify `requiresNew: true` to force create a new unit of work. + +```csharp +[UnitOfWork] +public async Task Method1() +{ + using (var uow = _unitOfWorkManager.Begin( + requiresNew: true, + isTransactional: true, + isolationLevel: IsolationLevel.RepeatableRead, + timeout: 30 + )) + { + await Method2(); + await uow.CompleteAsync(); + } +} +``` + +### 3. Use `virtual` Methods + +To be able to use Unit of Work attribute, you must use the `virtual` modifier for methods in dependency injection class services, because ABP Framework uses interceptors, and it cannot intercept non `virtual` methods, thus unable to implement Unit of Work functionality. + +### 4. Avoid Long Transactions + +Enabling long-running transactions can lead to resource locking, excessive transaction log usage, and reduced concurrent performance, while rollback costs are high and may exhaust database connection resources. It's recommended to split into shorter transactions, reduce lock holding time, and optimize performance and reliability. + +## Transaction-Related Recommendations + +- Choose appropriate transaction isolation levels based on business requirements +- Avoid overly long transactions, long-running operations should be split into multiple small transactions +- Use the `requiresNew` parameter reasonably to control transaction boundaries +- Pay attention to setting appropriate transaction timeout periods +- Ensure transactions can properly roll back when exceptions occur +- For read-only operations, it's recommended to use non-transactional Unit of Work to improve performance + +## References + +- [ABP Unit of Work](https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work) +- [EF Core Transactions](https://docs.microsoft.com/en-us/ef/core/saving/transactions) +- [Transaction Isolation Levels](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) diff --git a/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/pic.png b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/pic.png new file mode 100644 index 0000000000..d6805a806e Binary files /dev/null and b/docs/en/Community-Articles/2025-01-24-Understanding-Transactions-in-ABP-Unit-Of-Work/pic.png differ 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 new file mode 100644 index 0000000000..f5602650ae --- /dev/null +++ b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/POST.md @@ -0,0 +1,114 @@ +# Customizing Authentication Flow with OpenIddict Events in ABP Framework + +[ABP's OpenIddict Module](https://abp.io/docs/latest/modules/openiddict) provides an integration with the [OpenIddict](https://github.com/openiddict/openiddict-core) library, which provides advanced authentication features like **single sign-on**, **single log-out**, and **API access control**. + +OpenIddict provides an event-driven model ([event models](https://documentation.openiddict.com/introduction#events-model)) that allows developers to customize authentication and authorization processes. This event model enables handling actions such as user **sign-in**, **sign-out**, **token validation**, and **request handling** dynamically. + +In this article, we will explore OpenIddict event models, their key use cases, and how to implement them effectively. + +## 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. + +They are triggered during critical moments such as: + +* User authentication (sign-in) +* Session termination (sign-out) +* Token validation and generation +* Request processing +* Error handling + +OpenIddict provides multiple server events, under the `OpenIddictServerEvents` static class to make them easier to find (also provides additonal validation events under the `OpenIddictValidationEvents` static class). + +Here are some of the pre-defined `OpenIddictServerEvents`: + +![](openiddict-server-events.png) + +Each event represents a specific checkpoint in the **request processing pipeline**, such as validating an OpenID Connect request, extracting request parameters, processing the request, or generating a response. As an application developer, you simply need to create event handlers that subscribe to these predefined events to implement your custom logic at the desired pipeline stage. + +## Example: How to add custom logic when a user signs out? + +Let's walkthrough a practical example of implementing custom sign-out logic using OpenIddict events. + +### Step 1: Create a Custom Event Handler + +First, create a handler that implements `IOpenIddictServerHandler`: + +```csharp +using System.Threading.Tasks; +using OpenIddict.Server; + +namespace MySolution; + +public class SignOutEventHandler : IOpenIddictServerHandler +{ + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(100_000) + .SetType(OpenIddictServerHandlerType.Custom) + .Build(); + + public ValueTask HandleAsync(OpenIddictServerEvents.ProcessSignOutContext context) + { + // Implement your custom sign-out logic here + + // Examples: + // - Clear custom session data + // - Perform audit logging + // - Notify other services + // - Clean up user-specific resources + + return ValueTask.CompletedTask; + } +} +``` + +The handler configuration includes several important components: + +* `Descriptor` - Defines how the handler should be registered and executed +* `SetOrder` - Determines the execution order when multiple handlers exist +* `SetType` - Specifies this as a custom handler implementation +* `UseSingletonHandler` - Sets lifetime of the class as _Singleton_ + +### Step 2: Register the Event Handler + +Register your custom handler in your application's module configuration: + +```csharp +//... + +public class MySolutionAuthServerModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(serverBuilder => + { + serverBuilder.AddEventHandler(SignOutEventHandler.Descriptor); + }); + } + + //... +} +``` + +That's it! After these steps, your `SignOutEventHandler.HandleAsync()` method should be triggered after each signout request. You can also use other pre-defined server events for other stages of the authentication & authorization processes such as; + +* `OpenIddictServerEvents.ProcessSignInContext` -> after each sign-in, +* `OpenIddictServerEvents.ProcessErrorContext` -> when an error occurs in the authentication, +* `OpenIddictServerEvents.ProcessChallengeContext` -> called when processing a challenge operation, +* and other 40+ server events... + +Each event provides access to the relevant context, allowing you to access and modify the authentication flow's behavior. + +## 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. + +OpenIddict provides a powerful and flexible way to customize authentication flows. By leveraging these events, developers can implement complex authentication scenarios while maintaining clean, maintainable code. + +## References + +* [OpenIddict Documentation](https://documentation.openiddict.com/introduction#events-model) +* [ABP OpenIddict Module Documentation](https://abp.io/docs/latest/modules/openiddict) +* [Advanced OpenIddict Scenarios](https://kevinchalet.com/2018/07/02/implementing-advanced-scenarios-using-the-new-openiddict-rc3-events-model/) \ No newline at end of file diff --git a/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/cover-image.png b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/cover-image.png new file mode 100644 index 0000000000..11d9ff05ea Binary files /dev/null and b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/cover-image.png differ diff --git a/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/openiddict-server-events.png b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/openiddict-server-events.png new file mode 100644 index 0000000000..3514304f15 Binary files /dev/null and b/docs/en/Community-Articles/2025-02-04-OpenIddict-Custom-Logic/openiddict-server-events.png differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 720693ec75..87b3d56e88 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -487,7 +487,8 @@ }, { "text": "Exception Handling", - "path": "framework/fundamentals/exception-handling.md" + "path": "framework/fundamentals/exception-handling.md", + "keywords": ["Error"] }, { "text": "Localization", diff --git a/docs/en/framework/architecture/best-practices/mongodb-integration.md b/docs/en/framework/architecture/best-practices/mongodb-integration.md index 1930984e2e..c39f6443a0 100644 --- a/docs/en/framework/architecture/best-practices/mongodb-integration.md +++ b/docs/en/framework/architecture/best-practices/mongodb-integration.md @@ -114,7 +114,7 @@ public async Task FindByNormalizedUserNameAsync( bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync()) + return await (await GetQueryableAsync()) .FirstOrDefaultAsync( u => u.NormalizedUserName == normalizedUserName, GetCancellationToken(cancellationToken) @@ -125,10 +125,10 @@ public async Task FindByNormalizedUserNameAsync( `GetCancellationToken` fallbacks to the `ICancellationTokenProvider.Token` to obtain the cancellation token if it is not provided by the caller code. * **Do** ignore the `includeDetails` parameters for the repository implementation since MongoDB loads the aggregate root as a whole (including sub collections) by default. -* **Do** use the `GetMongoQueryableAsync()` method to obtain an `IQueryable` to perform queries wherever possible. Because; - * `GetMongoQueryableAsync()` method automatically uses the `ApplyDataFilters` method to filter the data based on the current data filters (like soft delete and multi-tenancy). +* **Do** use the `GetQueryableAsync()` method to obtain an `IQueryable` to perform queries wherever possible. Because; + * `GetQueryableAsync()` method automatically uses the `ApplyDataFilters` method to filter the data based on the current data filters (like soft delete and multi-tenancy). * Using `IQueryable` makes the code as much as similar to the EF Core repository implementation and easy to write and read. -* **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method. +* **Do** implement data filtering if it is not possible to use the `GetQueryableAsync()` method. ## Module Class diff --git a/docs/en/framework/architecture/domain-driven-design/repositories.md b/docs/en/framework/architecture/domain-driven-design/repositories.md index 9050e7d3c3..04848a59e8 100644 --- a/docs/en/framework/architecture/domain-driven-design/repositories.md +++ b/docs/en/framework/architecture/domain-driven-design/repositories.md @@ -393,19 +393,6 @@ This method is suggested; * If you are developing an application and you **don't plan to change** EF Core in the future, or you can **tolerate** it if you need to change it later. We believe that's reasonable if you are developing a final application. -#### MongoDB Case - -If you are using [MongoDB](../../data/mongodb), you need to add the [Volo.Abp.MongoDB](https://www.nuget.org/packages/Volo.Abp.MongoDB) NuGet package to your project. Even in this case, you can't directly use async LINQ extensions (like `ToListAsync`) because MongoDB doesn't provide async extension methods for `IQueryable`, but provides for `IMongoQueryable`. You need to cast the query to `IMongoQueryable` first to be able to use the async extension methods. - -**Example: Cast `IQueryable` to `IMongoQueryable` and use `ToListAsync()`** - -````csharp -var queryable = await _personRepository.GetQueryableAsync(); -var people = ((IMongoQueryable) queryable - .Where(p => p.Name.Contains(nameFilter))) - .ToListAsync(); -```` - ### Option-2: Use the IRepository Async Extension Methods ABP provides async extension methods for the repositories, just similar to async LINQ extension methods. diff --git a/docs/en/framework/infrastructure/blob-storing/bunny.md b/docs/en/framework/infrastructure/blob-storing/bunny.md new file mode 100644 index 0000000000..4c5fb5ef0f --- /dev/null +++ b/docs/en/framework/infrastructure/blob-storing/bunny.md @@ -0,0 +1,64 @@ +# BLOB Storing Bunny Provider + +BLOB Storing Bunny Provider can store BLOBs in [bunny.net Storage](https://bunny.net/storage/). + +> Read the [BLOB Storing document](../blob-storing) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Bunny BLOB as the storage provider. + +## Installation + +Use the ABP CLI to add [Volo.Abp.BlobStoring.Bunny](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Bunny) NuGet package to your project: + +* Install the [ABP CLI](../../../cli) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Bunny` package. +* Run `abp add-package Volo.Abp.BlobStoring.Bunny` command. + +If you want to do it manually, install the [Volo.Abp.BlobStoring.Bunny](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Bunny) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringBunnyModule))]` to the [ABP module](../../architecture/modularity/basics.md) class inside your project. + +## Configuration + +Configuration is done in the `ConfigureServices` method of your [module](../../architecture/modularity/basics.md) class, as explained in the [BLOB Storing document](../blob-storing). + +**Example: Configure to use the Bunny storage provider by default** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseBunny(Bunny => + { + Bunny.AccessKey = "your Bunny account access key"; + Bunny.Region = "the code of the main storage zone region"; // "de" is the default value + Bunny.ContainerName = "your bunny storage zone name"; + Bunny.CreateContainerIfNotExists = true; + }); + }); +}); + +```` + +> See the [BLOB Storing document](../blob-storing) to learn how to configure this provider for a specific container. + +### Options + +* **AccessKey** (string): Bunny Account Access Key. [Where do I find my Access key?](https://support.bunny.net/hc/en-us/articles/360012168840-Where-do-I-find-my-API-key) +* **Region** (string?): The code of the main storage zone region (Possible values: DE, NY, LA, SG). +* **ContainerName** (string): You can specify the container name in Bunny. If this is not specified, it uses the name of the BLOB container defined with the `BlobContainerName` attribute (see the [BLOB storing document](../blob-storing)). Please note that Bunny has some **rules for naming containers**: + * Storage Zone names must be a globaly unique. + * Storage Zone names must be between **4** and **64** characters long. + * Storage Zone names can consist only of **lowercase** letters, numbers, and hyphens (-). +* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Bunny, `BunnyBlobProvider` will try to create it. + +## Bunny Blob Name Calculator + +Bunny Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: + +* Appends `host` string if [current tenant](../../architecture/multi-tenancy) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](../blob-storing) to learn how to disable multi-tenancy for a container). +* Appends `tenants/` string if current tenant is not `null`. +* Appends the BLOB name. + +## Other Services + +* `BunnyBlobProvider` is the main service that implements the Bunny BLOB storage provider, if you want to override/replace it via [dependency injection](../../fundamentals/dependency-injection.md) (don't replace `IBlobProvider` interface, but replace `BunnyBlobProvider` class). +* `IBunnyBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultBunnyBlobNameCalculator` by default. +* `IBunnyClientFactory` is implemented by `DefaultBunnyClientFactory` by default. You can override/replace it,if you want customize. diff --git a/docs/en/framework/infrastructure/blob-storing/index.md b/docs/en/framework/infrastructure/blob-storing/index.md index f95a90e410..676757ad80 100644 --- a/docs/en/framework/infrastructure/blob-storing/index.md +++ b/docs/en/framework/infrastructure/blob-storing/index.md @@ -23,6 +23,7 @@ The ABP has already the following storage provider implementations: * [Minio](./minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/). * [Aws](./aws.md): Stores BLOBs on the [Amazon Simple Storage Service](https://aws.amazon.com/s3/). * [Google](./google.md): Stores BLOBs on the [Google Cloud Storage](https://cloud.google.com/storage). +* [Bunny](./bunny.md): Stores BLOBs on the [Bunny.net Storage](https://bunny.net/storage/). More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](./custom-provider.md) and [contribute](../../../contribution) to the ABP. diff --git a/docs/en/framework/infrastructure/entity-cache.md b/docs/en/framework/infrastructure/entity-cache.md index 03d0e8c548..64a03ad4c4 100644 --- a/docs/en/framework/infrastructure/entity-cache.md +++ b/docs/en/framework/infrastructure/entity-cache.md @@ -12,6 +12,11 @@ ABP provides an entity caching system that works on top of the [distributed cach ```csharp public class Product : AggregateRoot { + public Product(Guid id) + { + Id = id; + } + public string Name { get; set; } public string Description { get; set; } public float Price { get; set; } diff --git a/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-custom-content-above-filter.png b/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-custom-content-above-filter.png index ac81a885ec..3ad1593e42 100644 Binary files a/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-custom-content-above-filter.png and b/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-custom-content-above-filter.png differ diff --git a/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-form.png b/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-form.png index d0827ed369..cd8f091379 100644 Binary files a/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-form.png and b/docs/en/framework/ui/angular/images/angular-advanced-entity-filters-with-form.png differ diff --git a/docs/en/framework/ui/angular/images/angular-advanced-entity-filters.png b/docs/en/framework/ui/angular/images/angular-advanced-entity-filters.png index 5ae22b56a4..5a9b3f84ef 100644 Binary files a/docs/en/framework/ui/angular/images/angular-advanced-entity-filters.png and b/docs/en/framework/ui/angular/images/angular-advanced-entity-filters.png differ diff --git a/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md b/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md index 970d35b851..043f811d49 100644 --- a/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md +++ b/docs/en/framework/ui/mvc-razor-pages/javascript-api/notify.md @@ -1,6 +1,6 @@ # ASP.NET Core MVC / Razor Pages UI: JavaScript Notify API -Notify API is used to show toast style, auto disappearing UI notifications to the end user. It is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. +Notify API is used to show toast style, auto disappearing UI notifications to the end user. ## Quick Example @@ -26,20 +26,23 @@ There are four types of pre-defined notifications; * `abp.notify.warn(...)` * `abp.notify.error(...)` -All of the methods above gets the following parameters; +All of the methods above accept the following parameters: * `message`: A message (`string`) to show to the user. * `title`: An optional title (`string`). -* `options`: Additional options to be passed to the underlying library, to the Toastr by default. - -## Toastr Configuration - -The notification API is implemented by the [Toastr](https://github.com/CodeSeven/toastr) library by default. You can see its own configuration options. - -**Example: Show toast messages on the top right of the page** - -````js -toastr.options.positionClass = 'toast-top-right'; -```` - -> ABP sets this option to `toast-bottom-right` by default. You can override it just as shown above. \ No newline at end of file +* `options`: Additional options to customize the notification. Available options: + * `life`: Display duration in milliseconds (default: `5000`) + * `sticky`: Keep toast visible until manually closed (default: `false`) + * `closable`: Show close button (default: `true`) + * `tapToDismiss`: Click anywhere on toast to dismiss (default: `false`) + * `containerKey`: Key for multiple container support (optional) + * `iconClass`: Custom icon class (optional) + * `position`: Position configuration (optional) + * `top`: Distance from top (default: `'auto'`) + * `right`: Distance from right (default: `'30px'`) + * `bottom`: Distance from bottom (default: `'30px'`) + * `left`: Distance from left (default: `'auto'`) + +## Global Configuration + +`AbpToastService.setDefaultOptions` method can be used to set default options for all notifications. This method should be called before any notification is shown. diff --git a/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md b/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md index 9230075ed3..b59ecf8dd3 100644 --- a/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md +++ b/docs/en/framework/ui/mvc-razor-pages/tag-helpers/form-elements.md @@ -10,7 +10,7 @@ See the [form elements demo page](https://bootstrap-taghelpers.abp.io/Components ## abp-input -`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`. +`abp-input` tag creates a Bootstrap form input for a given c# property. It uses [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-9.0#the-input-tag-helper) in background, so every data annotation attribute of `input` tag helper of Asp.Net Core is also valid for `abp-input`. Usage: @@ -89,7 +89,7 @@ You can set some of the attributes on your c# property, or directly on HTML tag. * `required-symbol`: Adds the required symbol `(*)` to the label when the input is required. The default value is `True`. * `floating-label`: Sets the label as floating label. The default value is `False`. -`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-7.0#the-input-tag-helper) are also valid for `abp-input` tag helper. +`asp-format`, `name` and `value` attributes of [Asp.Net Core Input Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-9.0#the-input-tag-helper) are also valid for `abp-input` tag helper. ### Label & Localization @@ -101,7 +101,7 @@ You can set the label of the input in several ways: ## abp-select -`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [ASP.NET Core Select Tag Helper](https://docs.microsoft.com/tr-tr/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of ASP.NET Core is also valid for `abp-select`. +`abp-select` tag creates a Bootstrap form select for a given c# property. It uses [ASP.NET Core Select Tag Helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-9.0#the-select-tag-helper) in background, so every data annotation attribute of `select` tag helper of ASP.NET Core is also valid for `abp-select`. `abp-select` tag needs a list of `Microsoft.AspNetCore.Mvc.Rendering.SelectListItem ` to work. It can be provided by `asp-items` attriube on the tag or `[SelectItems()]` attribute on c# property. (if you are using [abp-dynamic-form](dynamic-forms.md), c# attribute is the only way.) @@ -432,4 +432,4 @@ newPicker.insertAfter($('body')); * `startDateName`: Sets the name of the hidden start date input. * `endDateName`: Sets the name of the hidden end date input. * `dateName`: Sets the name of the hidden date input. -* Other [datepicker options](https://www.daterangepicker.com/#options). Eg: `startDate: "2020-01-01"`. \ No newline at end of file +* Other [datepicker options](https://www.daterangepicker.com/#options). Eg: `startDate: "2020-01-01"`. diff --git a/docs/en/guides/microservice-mongodb.md b/docs/en/guides/microservice-mongodb.md index c9b63ecf4e..087668ba7a 100644 --- a/docs/en/guides/microservice-mongodb.md +++ b/docs/en/guides/microservice-mongodb.md @@ -97,9 +97,9 @@ Here we use `BookStore.ProductService` project as an example: int skipCount = 0, CancellationToken cancellationToken = default) { - var query = ApplyFilter(await GetMongoQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax); + var query = ApplyFilter(await GetQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax); query = query.OrderBy(string.IsNullOrWhiteSpace(sorting) ? ProductConsts.GetDefaultSorting(false) : sorting); - return await query.As>().PageBy>(skipCount, maxResultCount).ToListAsync(cancellationToken); + return await query.PageBy(skipCount, maxResultCount).ToListAsync(cancellationToken); } public async Task GetCountAsync( @@ -109,8 +109,8 @@ Here we use `BookStore.ProductService` project as an example: float? priceMax = null, CancellationToken cancellationToken = default) { - var query = ApplyFilter(await GetMongoQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax); - return await query.As>().LongCountAsync(GetCancellationToken(cancellationToken)); + var query = ApplyFilter(await GetQueryableAsync(cancellationToken), filterText, name, priceMin, priceMax); + return await query.LongCountAsync(GetCancellationToken(cancellationToken)); } protected virtual IQueryable ApplyFilter( diff --git a/docs/en/images/js-notify-success.png b/docs/en/images/js-notify-success.png index d5300f3468..1068bf8621 100644 Binary files a/docs/en/images/js-notify-success.png and b/docs/en/images/js-notify-success.png differ diff --git a/docs/en/release-info/migration-guides/MongoDB-Driver-2-to-3.md b/docs/en/release-info/migration-guides/MongoDB-Driver-2-to-3.md new file mode 100644 index 0000000000..79c0d70573 --- /dev/null +++ b/docs/en/release-info/migration-guides/MongoDB-Driver-2-to-3.md @@ -0,0 +1,59 @@ +# Migrating from MongoDB Driver 2 to 3 + +## Introduction + +The release of MongoDB Driver 3 includes numerous user-requested fixes and improvements that were deferred in previous versions due to backward compatibility concerns. It also features internal improvements to reduce technical debt and enhance maintainability. One major update is the removal of a significant portion of the public API (primarily from `MongoDB.Driver.Core`), which was not intended for public use. The removed APIs were marked as deprecated in version 2.30.0. + +Please refer to the [upgrade guide](https://www.mongodb.com/docs/drivers/csharp/current/upgrade/v3/) for a complete list of breaking changes and upgrade guidelines. + +## Repository Changes + +Some method signatures in the `MongoDbRepository` class have been updated because the `IMongoQueryable` has been removed. The specific changes are as follows: + +- The new `GetQueryableAsync` method has been added to return `IQueryable`. +- The `GetMongoQueryable` and `GetMongoQueryableAsync` methods return `IQueryable` instead of `IMongoQueryable`, +- The `GetMongoQueryable` and `GetMongoQueryableAsync` methods are marked as obsolete, You should use the new `GetQueryableAsync` method instead. + +Please update your application by searching for and replacing these method calls. + +> The return value of the `GetQueryableAsync` method is `IQueryable`, which can be used directly to perform queries, similar to EF Core. Remove all instances of `IMongoQueryable` in your project and replace them with `IQueryable`. + +**Previous code example:** + +```csharp +var myEntity = await (await GetMongoQueryableAsync()).As>().FirstOrDefaultAsync(x => x.Id == id); +``` + +**Updated code example:** + +```csharp +var myEntity = await GetQueryableAsync().FirstOrDefaultAsync(x => x.Id == id); +``` + +## Unit Test Changes + +Previously, we used the [EphemeralMongo](https://github.com/asimmon/ephemeral-mongo) library for unit testing. However, it does not support the latest version of [MongoDB.Driver 3.x](https://github.com/mongodb/mongo-go-driver). You should replace it with [MongoSandbox](https://github.com/wassim-k/MongoSandbox). + +In your unit test project files, replace the following: + +```xml + + + + +``` + +With: + +```xml + + + + +``` + +In your unit test classes, replace `using EphemeralMongo` with `using MongoSandbox`. + +## Official Upgrade Guide + +We recommend reviewing the [upgrade guide](https://www.mongodb.com/docs/drivers/csharp/current/upgrade/v3/) for MongoDB Driver 3 to ensure a smooth migration process. diff --git a/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md b/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md index a9e106221b..fe2d226d53 100644 --- a/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md +++ b/docs/en/solution-templates/layered-web-application/deployment/deployment-docker-compose.md @@ -15,7 +15,7 @@ This guide will guide you through how to build docker images for your applicatio ## Building Docker Images -Each application contains a dockerfile called `Dockerfile.local` for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the project to be built in `Release` mode to create the image. Currently, if you are building your images using CI & CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own [multi-stage dockerfiles](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-7.0). +Each application contains a dockerfile called `Dockerfile.local` for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the project to be built in `Release` mode to create the image. Currently, if you are building your images using CI & CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own [multi-stage dockerfiles](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-9.0). Since they are not multi-staged Dockerfiles, if you want to build the images individually, you can navigate to the related to-be-hosted application folder and run the following command: @@ -31,7 +31,7 @@ docker build -f Dockerfile.local -t mycompanyname/myappname:version . To manually build your application image. -To ease the process, application templates provide a build script to build all the images with a single script under `etc/build` folder named `build-images-locally.ps1`. +To ease the process, application templates provide a build script to build all the images with a single script under `etc/docker-compose` folder named `build-images-locally.ps1`. Based on your application name, UI and type, a build image script will be generated. {{ if UI == "MVC"}} @@ -204,8 +204,8 @@ DbMigrator is a console application that is used to migrate the database of your `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "BookStore.DbMigrator.dll"] ``` @@ -226,7 +226,7 @@ docker build -f Dockerfile.local -t acme/bookstore-db-migrator:latest . #Builds In the **WebModule** under authentication configuration, there is an extra configuration for containerized environment support: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -268,13 +268,13 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect. -The `appsettings.json` file does not contain `AuthServer:IsContainerizedOnLocalhost` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. +The `appsettings.json` file does not contain `AuthServer:IsOnK8s` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "Acme.BookStore.Web.dll"] ``` @@ -289,11 +289,11 @@ docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the imag ​ {{ end }} {{ if Tiered == "No" }}MVC/Razor Pages application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -318,36 +318,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **WebModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **Web** folder and run: @@ -369,7 +350,7 @@ docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the imag In the **BlazorModule** under authentication configuration, there is an extra configuration for containerized environment support: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -411,13 +392,13 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) This is used when the **AuthServer is running on docker containers(or pods)** to configure the redirection URLs for the internal network and the web. The application must be redirected to real DNS (localhost in this case) when the `/authorize` and `/logout` requests over the browser but handle the token validation inside the isolated network without going out to the internet. `"AuthServer:MetaAddress"` appsetting should indicate the container/pod service name while the `AuthServer:Authority` should be pointing to real DNS for the browser to redirect. -The `appsettings.json` file does not contain `AuthServer:IsContainerizedOnLocalhost` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. +The `appsettings.json` file does not contain `AuthServer:IsOnK8s` and `AuthServer:MetaAddress` settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the `docker-compose.yml` file. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "Acme.BookStore.Blazor.dll"] ``` @@ -432,11 +413,11 @@ docker build -f Dockerfile.local -t acme/bookstore-blazor:latest . #Builds the i ​ {{ end }} {{ if Tiered == "No" }}Blazor Server application is a server-side rendering application that contains both the OpenID-provider and the Http.Api endpoints within self; it will be a single application to deploy. `Dockerfile.local` is provided under this project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -461,36 +442,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **BlazorModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **BlazorModule** folder and run: @@ -562,7 +524,7 @@ server { } ``` -This configuration allows returning the `dynamic-env.json` file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. **The `dynamic-env.json` file you need to override is located under `aspnet-core/etc/docker`** folder. +This configuration allows returning the `dynamic-env.json` file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. **The `dynamic-env.json` file you need to override is located under `aspnet-core/etc/docker-compose`** folder. ​ {{ if Tiered == "No" }} @@ -645,8 +607,8 @@ docker build -f Dockerfile.local -t acme/bookstore-angular:latest . #Builds the The Blazor application uses [nginx:alpine-slim](https://hub.docker.com/layers/library/nginx/alpine-slim/images/sha256-0f859db466fda2c52f62b48d0602fb26867d98edbd62c26ae21414b3dea8d8f4?context=explore) base image to host the blazor application. You can modify the base image based on your preference in the `Dockerfile.local` which provided under the Blazor folder of your solution as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS build -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS build +COPY bin/Release/net9.0/publish/ app/ FROM nginx:alpine-slim AS final WORKDIR /usr/share/nginx/html @@ -701,11 +663,11 @@ docker build -f Dockerfile.local -t acme/bookstore-blazor:latest . #Builds the i This is the backend application that contains the openid-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -730,36 +692,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **HttpApiHostModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside of the dockerfile. You need to keep in mind to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run: @@ -777,11 +720,11 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag This is the backend application that contains the OpenID-provider functionality as well. The `dockerfile.local` is located under the `Http.Api.Host` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -806,36 +749,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` -This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different customization options. - -The `GetSigningCertificate` method is a private method located under the same **HttpApiHostModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` +This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to **Http.Api.Host** folder and run: @@ -855,11 +779,11 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag This is the openid-provider application, the authentication server, which should be individually hosted compared to non-tiered application templates. The `dockerfile.local` is located under the `AuthServer` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED @@ -884,36 +808,17 @@ if (!hostingEnvironment.IsDevelopment()) options.AddDevelopmentEncryptionAndSigningCertificate = false; }); - PreConfigure(builder => + PreConfigure(serverBuilder => { - builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); - builder.SetIssuer(new Uri(configuration["AuthServer:Authority"])); + serverBuilder.AddProductionEncryptionAndSigningCertificate("openiddict.pfx", configuration["AuthServer:CertificatePassPhrase"]!); + serverBuilder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!)); }); } ``` This configuration disables the *DevelopmentEncryptionAndSigningCertificate* and uses a self-signed certificate called `authserver.pfx`. for **signing and encrypting the tokens**. This certificate is created when the docker image is built using the `dotnet dev-certs` tooling. It is a sample-generated certificate, and it is **recommended** to update it for the production environment. You can check the [OpenIddict Encryption and signing credentials documentation](https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html) for different options and customization. -The `GetSigningCertificate` method is a private method located under the same **AuthServerModule**: - -```csharp -private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) -{ - var fileName = "authserver.pfx"; - var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; - var file = Path.Combine(hostingEnv.ContentRootPath, fileName); - - if (!File.Exists(file)) - { - throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); - } - - return new X509Certificate2(file, passPhrase); -} -``` - -> You can always create any self-signed certificate using any other tooling outside the dockerfile. You need to remember to set them as **embedded resource** since the `GetSigningCertificate` method will be checking this file physically. +> You can always create any self-signed certificate using any other tooling outside the Dockerfile. You need to remember to set them as **embedded resource**. If you don't want to use the `build-images-locally.ps1` to build the images or to build this image individually and manually, navigate to the **AuthServer** folder and run: @@ -927,8 +832,8 @@ docker build -f Dockerfile.local -t acme/bookstore-authserver:latest . #Builds t This is the backend application that exposes the endpoints and swagger UI. It is not a multi-stage dockerfile; hence you need to have already built this application in **Release mode** to use this dockerfile. The `dockerfile.local` is located under the `Http.Api.Host` project as below; ```dockerfile -FROM mcr.microsoft.com/dotnet/aspnet:7.0 -COPY bin/Release/net7.0/publish/ app/ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +COPY bin/Release/net9.0/publish/ app/ WORKDIR /app ENTRYPOINT ["dotnet", "Acme.BookStore.HttpApi.Host.dll"] ``` @@ -944,7 +849,7 @@ docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the imag ## Running Docker-Compose on Localhost -Under the `etc/docker` folder, you can find the `docker-compose.yml` to run your application. To ease the running process, the template provides `run-docker.ps1` (and `run-docker.sh`) scripts that handle the HTTPS certificate creation, which is used in environment variables; +Under the `etc/docker-compose` folder, you can find the `docker-compose.yml` to run your application. To ease the running process, the template provides `run-docker.ps1` (and `run-docker.sh`) scripts that handle the HTTPS certificate creation, which is used in environment variables; ```powershell $currentFolder = $PSScriptRoot @@ -1203,7 +1108,7 @@ This is the angular application we deploy on http://localhost:4200 by default us > Don't forget to rebuild the `acme/bookstore-angular:latest` image after updating the `nginx.conf` file. -The bookstore-angular service mounts the `etc/docker/dynamic-env.json` file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. **Do not forget to override the `dynamic-env.json` located under the `aspnet-core/etc/docker`** folder. +The bookstore-angular service mounts the `etc/docker-compose/dynamic-env.json` file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. **Do not forget to override the `dynamic-env.json` located under the `aspnet-core/etc/docker-compose`** folder. > If you are not using Docker with WSL, you may have problems with the volume mount permissions. You need to grant docker to be able to use the local file system. See this [SO answer](https://stackoverflow.com/a/20652410) for more information. @@ -1438,7 +1343,7 @@ bookstore-web: - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc - App__SelfUrl=https://localhost:44353 - AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }} - - AuthServer__IsContainerizedOnLocalhost=true + - AuthServer__IsOnK8s=true - AuthServer__Authority=https://localhost:44334/ - RemoteServices__Default__BaseUrl=http://bookstore-api - RemoteServices__AbpAccountPublic__BaseUrl=http://bookstore-authserver @@ -1464,7 +1369,7 @@ This is the MVC/Razor Page application docker service is using the `acme/booksto The MVC/Razor Page is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -1511,7 +1416,7 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) {{ if Tiered == "Yes" }} -- `AuthServer__IsContainerizedOnLocalhost` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercepting the URLS for *authorization* and *logout* endpoints. +- `AuthServer__IsOnK8s` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercepting the URLS for *authorization* and *logout* endpoints. - `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default. @@ -1691,7 +1596,7 @@ bookstore-blazor: - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc - App__SelfUrl=https://localhost:44314 - AuthServer__RequireHttpsMetadata=false {{ if Tiered == "Yes" }} - - AuthServer__IsContainerizedOnLocalhost=true + - AuthServer__IsOnK8s=true - AuthServer__Authority=https://localhost:44334/ - AuthServer__MetaAddress=http://bookstore-authserver - RemoteServices__Default__BaseUrl=http://bookstore-api @@ -1718,7 +1623,7 @@ This is the Blazor Server application Docker service is using the `acme/bookstor The Blazor Server is a server-side rendering application that uses the **hybrid flow**. This flow uses **browser** to login/logout process to the OpenID-provider but issues the **access_token from the back-channel** (server-side). To achieve this functionality, the module class has extra `OpenIdConnectOptions` to override some of the events: ```csharp -if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) +if (Convert.ToBoolean(configuration["AuthServer:IsOnK8s"])) { context.Services.Configure("oidc", options => { @@ -1765,7 +1670,7 @@ if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"])) {{ if Tiered == "Yes" }} -- `AuthServer__IsContainerizedOnLocalhost` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercept the URLS for *authorization* and *logout* endpoints. +- `AuthServer__IsOnK8s` is the configuration to enable the **OpenIdConnectOptions** to provide a different endpoint for the MetaAddress of the OpenID-provider and intercept the URLS for *authorization* and *logout* endpoints. - `AuthServer__MetaAddress` is the `.well-known/openid-configuration` endpoint for issuing the access_token and internal token validation. It is the containerized `http://bookstore-authserver` by default. diff --git a/docs/en/solution-templates/layered-web-application/solution-structure.md b/docs/en/solution-templates/layered-web-application/solution-structure.md index 680df73a59..3afd1b1274 100644 --- a/docs/en/solution-templates/layered-web-application/solution-structure.md +++ b/docs/en/solution-templates/layered-web-application/solution-structure.md @@ -214,3 +214,7 @@ This project is an application that hosts the API of the solution. It has its ow Just like the default structure, this project contains the User Interface (UI) of the application. It contains razor pages, JavaScript files, style files, images and so on... This project contains an `appsettings.json` file, but this time it does not have a connection string because it never connects to the database. Instead, it mainly contains the endpoint of the remote API server and the authentication server. + +### Docker Compose + +The **docker-compose** configuration files in the `etc/docker-compose` folder is configured to run the solution with Docker. See [Docker Deployment using Docker Compose](deployment/deployment-docker-compose.md) for more information. \ No newline at end of file diff --git a/docs/en/studio/running-applications.md b/docs/en/studio/running-applications.md index 9e266ef7a9..5392250153 100644 --- a/docs/en/studio/running-applications.md +++ b/docs/en/studio/running-applications.md @@ -230,3 +230,11 @@ CLI applications uses the [powershell](https://learn.microsoft.com/en-us/powersh - `Remove`: This option allows you to delete the selected application. > When CLI applications start chain icon won't be visible, because only C# applications can connect the ABP Studio. + +## Docker Compose + +You can manually run applications using [Docker Compose](https://docs.docker.com/compose/). This allows for easy setup and management of multi-container Docker applications. To get started, ensure you have Docker and Docker Compose installed on your machine. + +Refer to the [Deployment with Docker Compose](../solution-templates/layered-web-application/deployment/deployment-docker-compose.md) documentation for detailed instructions on how to configure and run your applications using `docker-compose`. + +> Note: The **Docker Compose** is not available in the ABP Studio interface. \ No newline at end of file diff --git a/docs/en/tutorials/book-store/part-07.md b/docs/en/tutorials/book-store/part-07.md index 0708ecfcb9..92108c4a35 100644 --- a/docs/en/tutorials/book-store/part-07.md +++ b/docs/en/tutorials/book-store/part-07.md @@ -180,7 +180,7 @@ public class MongoDbAuthorRepository public async Task FindByNameAsync(string name) { - var queryable = await GetMongoQueryableAsync(); + var queryable = await GetQueryableAsync(); return await queryable.FirstOrDefaultAsync(author => author.Name == name); } @@ -190,14 +190,13 @@ public class MongoDbAuthorRepository string sorting, string filter = null) { - var queryable = await GetMongoQueryableAsync(); + var queryable = await GetQueryableAsync(); return await queryable - .WhereIf>( + .WhereIf>( !filter.IsNullOrWhiteSpace(), author => author.Name.Contains(filter) ) .OrderBy(sorting) - .As>() .Skip(skipCount) .Take(maxResultCount) .ToListAsync(); diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index c3ca5b63b2..ca471bb304 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -470,6 +470,7 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling", "src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling.csproj", "{2F9BA650-395C-4BE0-8CCB-9978E753562A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling", "src\Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling\Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling.csproj", "{7ADB6D92-82CC-4A2A-8BCF-FC6C6308796D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google", "src\Volo.Abp.BlobStoring.Google\Volo.Abp.BlobStoring.Google.csproj", "{DEEB5200-BBF9-464D-9B7E-8FC035A27E94}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Google.Tests", "test\Volo.Abp.BlobStoring.Google.Tests\Volo.Abp.BlobStoring.Google.Tests.csproj", "{40FB8907-9CF7-44D0-8B5F-538AC6DAF8B9}" @@ -480,6 +481,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.TencentCloud", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.TencentCloud.Tests", "test\Volo.Abp.Sms.TencenCloud.Tests\Volo.Abp.Sms.TencentCloud.Tests.csproj", "{C753DDD6-5699-45F8-8669-08CE0BB816DE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny", "src\Volo.Abp.BlobStoring.Bunny\Volo.Abp.BlobStoring.Bunny.csproj", "{1BBCBA72-CDB6-4882-96EE-D4CD149433A2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny.Tests", "test\Volo.Abp.BlobStoring.Bunny.Tests\Volo.Abp.BlobStoring.Bunny.Tests.csproj", "{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1434,6 +1439,14 @@ Global {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {C753DDD6-5699-45F8-8669-08CE0BB816DE}.Release|Any CPU.Build.0 = Release|Any CPU + {1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Release|Any CPU.Build.0 = Release|Any CPU + {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1676,6 +1689,8 @@ Global {E50739A7-5E2F-4EB5-AEA9-554115CB9613} = {447C8A77-E5F0-4538-8687-7383196D04EA} {BE7109C5-7368-4688-8557-4A15D3F4776A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {C753DDD6-5699-45F8-8669-08CE0BB816DE} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {1BBCBA72-CDB6-4882-96EE-D4CD149433A2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs index 9d1bac7f5f..115d6eeb02 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs @@ -106,11 +106,11 @@ public class AbpPaginationTagHelperService : AbpTagHelperService GetPreviousButtonAsync(TagHelperContext context, TagHelperOutput output) { var localizationKey = "PagerPrevious"; - var currentPage = TagHelper.Model.CurrentPage == 1 - ? TagHelper.Model.CurrentPage.ToString() - : (TagHelper.Model.CurrentPage - 1).ToString(); + var currentPage = TagHelper.Model.CurrentPage > 1 + ? (TagHelper.Model.CurrentPage - 1).ToString() + : "1"; return - "
  • \r\n" + + "
  • \r\n" + (await RenderAnchorTagHelperLinkHtmlAsync(context, output, currentPage, localizationKey)) + "
  • "; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs index 227bc0fd71..8d5700159f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabTagHelperService.cs @@ -89,7 +89,7 @@ public class AbpTabTagHelperService : AbpTagHelperService var id = TagHelper.Name; var attributes = GetTabContentAttributes(context, output); - var classAttributesAsString = attributes.Where(a => a.Name == "class").ToList().Select(a => a.Name).JoinAsString(" "); + var classAttributesAsString = attributes.Where(a => a.Name == "class").ToList().Select(a => a.Value).JoinAsString(" "); var otherAttributes = attributes.Where(a => a.Name != "class").ToList(); var wrapper = new TagBuilder("div"); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrScriptBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrScriptBundleContributor.cs deleted file mode 100644 index 226b076b7a..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrScriptBundleContributor.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; -using Volo.Abp.Modularity; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; - -[DependsOn(typeof(JQueryScriptContributor))] -public class ToastrScriptBundleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/toastr/toastr.min.js"); - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrStyleBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrStyleBundleContributor.cs deleted file mode 100644 index d00f9e3768..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Toastr/ToastrStyleBundleContributor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; - -public class ToastrStyleBundleContributor : BundleContributor -{ - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/libs/toastr/toastr.min.css"); - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs index 5d8c15ba40..4bb1d80fef 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs @@ -12,7 +12,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.SweetAlert2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; @@ -26,7 +25,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; typeof(Select2ScriptContributor), typeof(DatatablesNetBs5ScriptContributor), typeof(Sweetalert2ScriptContributor), - typeof(ToastrScriptBundleContributor), typeof(MalihuCustomScrollbarPluginScriptBundleContributor), typeof(LuxonScriptContributor), typeof(TimeagoScriptContributor), @@ -48,7 +46,7 @@ public class SharedThemeGlobalScriptContributor : BundleContributor "/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/authentication-state/authentication-state-listener.js" }); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs index f959d73f05..4f436a02b0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs @@ -7,7 +7,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs5; using Volo.Abp.AspNetCore.Mvc.UI.Packages.FontAwesome; using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; -using Volo.Abp.AspNetCore.Mvc.UI.Packages.Toastr; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; @@ -16,7 +15,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; typeof(CoreStyleContributor), typeof(BootstrapStyleContributor), typeof(FontAwesomeStyleContributor), - typeof(ToastrStyleBundleContributor), typeof(Select2StyleContributor), typeof(MalihuCustomScrollbarPluginStyleBundleContributor), typeof(DatatablesNetBs5StyleContributor), @@ -30,7 +28,8 @@ public class SharedThemeGlobalStyleContributor : BundleContributor context.Files.AddRange(new BundleFile[] { "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-styles.css", - "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css" + "/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-styles.css", + "/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css", }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css new file mode 100644 index 0000000000..b6edbb0d6f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.css @@ -0,0 +1,139 @@ +.abp-toast-container { + position: fixed; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + min-width: 350px; + min-height: 80px; + z-index: 1900; + right: 30px; + bottom: 30px; +} + +.abp-toast { + display: grid; + grid-template-columns: 35px 1fr; + gap: 5px; + margin: 5px 0; + padding: 10px; + width: 350px; + user-select: none; + z-index: 9999; + color: #fff; + border-radius: 8px; + font-size: 16px; + box-shadow: 0 0 20px 0 rgba(76, 87, 125, 0.02); + animation: toastIn 0.3s ease-in-out; +} + +.abp-toast-success { + border: 2px solid #4fbf67; + background-color: #4fbf67; +} + +.abp-toast-error { + border: 2px solid #c00d49; + background-color: #c00d49; +} + +.abp-toast-info { + border: 2px solid #438aa7; + background-color: #438aa7; +} + +.abp-toast-warning { + border: 2px solid #ff9f38; + background-color: #ff9f38; +} + +.abp-toast-icon { + display: flex; + align-items: center; + justify-content: center; +} + +.abp-toast-icon .icon { + font-size: 30px; +} + +.abp-toast-content { + position: relative; + display: flex; + align-self: center; + flex-direction: column; + word-break: break-word; + padding-bottom: 2px; +} + +.abp-toast-close-button { + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 0px 5px 0 0; + width: 25px; + height: 100%; + border: none; + border-radius: 50%; + background: transparent; + color: inherit; + cursor: pointer; +} + +.abp-toast-close-button:focus { + outline: none; +} + +.abp-toast-title { + margin: 0; + padding: 0; + font-size: 1rem; + font-weight: 600; +} + +.abp-toast-message { + margin: 0; + padding: 0; + max-width: 240px; +} + +@keyframes toastIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes toastOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +.toast-removing { + animation: toastOut 0.3s ease-in-out forwards; +} + +@media only screen and (max-width: 768px) { + .abp-toast-container { + min-width: 100%; + right: 0; + } + + .abp-toast { + width: 95%; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js new file mode 100644 index 0000000000..a23d844ce6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toast/abp-toast.js @@ -0,0 +1,207 @@ +function AbpToastService(globalOptions) { + // Static default configuration for all instances + if (!AbpToastService.defaultOptions) { + AbpToastService.defaultOptions = { + closable: true, + sticky: false, + life: 5000, + tapToDismiss: false, + containerKey: undefined, + iconClass: undefined, + position: { + top: 'auto', + right: '30px', + bottom: '30px', + left: 'auto' + } + }; + } + + // Find existing container or create new one + const containerId = globalOptions?.containerKey ? + `toast-container-${globalOptions.containerKey}` : + 'toast-container'; + + this.container = document.getElementById(containerId); + if (!this.container) { + this.container = document.createElement('div'); + this.container.id = containerId; + this.container.className = 'abp-toast-container'; + document.body.appendChild(this.container); + } + + this.toasts = []; + this.lastId = 0; + + // Merge user provided global options with defaults + this.globalOptions = this.extend({}, AbpToastService.defaultOptions, globalOptions); + + this.updateContainerPosition(); +} + +// Deep merge objects +AbpToastService.prototype.extend = function(target, ...sources) { + sources.forEach(source => { + for (const key in source) { + if (source[key] && typeof source[key] === 'object') { + target[key] = this.extend(target[key] || {}, source[key]); + } else { + target[key] = source[key]; + } + } + }); + return target; +}; + +// Update toast container position based on global options +AbpToastService.prototype.updateContainerPosition = function() { + const { position } = this.globalOptions; + Object.assign(this.container.style, position); +}; + +// Update global options +AbpToastService.prototype.setGlobalOptions = function(options) { + this.globalOptions = this.extend({}, this.globalOptions, options); + this.updateContainerPosition(); +}; + +// Get icon class based on severity +AbpToastService.prototype.getIconClass = function(severity, options) { + // Use custom icon class if provided + if (options.iconClass) { + return options.iconClass; + } + + const icons = { + success: 'fa fa-check', + info: 'fa fa-info-circle', + warning: 'fa fa-exclamation-triangle', + error: 'fa fa-exclamation-circle' + }; + return icons[severity] || 'fa fa-exclamation-triangle'; +}; + +// Create toast DOM element +AbpToastService.prototype.createToastElement = function(message, title, severity, options) { + const toast = document.createElement('div'); + toast.className = `abp-toast abp-toast-${severity}`; + + const closeButton = options.closable !== false ? + `` : ''; + + const titleHtml = title ? `
    ${title}
    ` : ''; + + toast.innerHTML = ` +
    + +
    +
    + ${closeButton} + ${titleHtml} +

    ${message}

    +
    `; + + // Add event listeners + const closeButtonElement = toast.querySelector('.abp-toast-close-button'); + if (closeButtonElement) { + closeButtonElement.addEventListener('click', () => this.remove(toast)); + } + + if (options.tapToDismiss) { + toast.addEventListener('click', () => this.remove(toast)); + } + + return toast; +}; + +// Show a toast with given options +AbpToastService.prototype.show = function(message, title, severity = 'neutral', options = {}) { + const mergedOptions = this.extend({}, this.globalOptions, options); + const id = ++this.lastId; + const toast = this.createToastElement(message, title, severity, mergedOptions); + + // Set data attributes for non-object options + Object.entries(mergedOptions) + .filter(([_, value]) => typeof value !== 'object') + .forEach(([key, value]) => toast.dataset[key] = value); + + toast.dataset.id = id; + this.container.appendChild(toast); + this.toasts.push(toast); + + // Auto remove if not sticky + if (!mergedOptions.sticky) { + setTimeout(() => this.remove(toast), mergedOptions.life); + } + + return id; +}; + +// Remove a toast with animation +AbpToastService.prototype.remove = function(toastElement) { + toastElement.classList.add('toast-removing'); + setTimeout(() => { + if (toastElement.parentNode === this.container) { + this.container.removeChild(toastElement); + } + this.toasts = this.toasts.filter(t => t !== toastElement); + }, 300); // Match animation duration +}; + +// Convenience methods for different severities +AbpToastService.prototype.success = function(message, title, options) { + return this.show(message, title, 'success', options); +}; + +AbpToastService.prototype.error = function(message, title, options) { + return this.show(message, title, 'error', options); +}; + +AbpToastService.prototype.info = function(message, title, options) { + return this.show(message, title, 'info', options); +}; + +AbpToastService.prototype.warning = function(message, title, options) { + return this.show(message, title, 'warning', options); +}; + +// Clear all toasts +AbpToastService.prototype.clear = function(containerKey) { + if (containerKey) { + this.toasts = this.toasts.filter(toast => { + const shouldRemove = toast.dataset.containerKey === containerKey; + if (shouldRemove) { + this.remove(toast); + } + return !shouldRemove; + }); + } else { + this.toasts.forEach(toast => this.remove(toast)); + } +}; + +// Static method to set default options for all instances +AbpToastService.setDefaultOptions = function(options) { + AbpToastService.defaultOptions = this.prototype.extend({}, AbpToastService.defaultOptions, options); +}; + +var abp = abp || {}; +(function () { + abp.notify.success = function (message, title, options) { + new AbpToastService().success(message, title, options); + }; + + abp.notify.info = function (message, title, options) { + new AbpToastService().info(message, title, options); + }; + + abp.notify.warn = function (message, title, options) { + new AbpToastService().warning(message, title, options); + }; + + abp.notify.error = function (message, title, options) { + new AbpToastService().error(message, title, options); + }; +})(); \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js deleted file mode 100644 index 37a7c40166..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js +++ /dev/null @@ -1,34 +0,0 @@ -var abp = abp || {}; -(function () { - - if (!toastr) { - return; - } - - /* DEFAULTS *************************************************/ - - toastr.options.positionClass = 'toast-bottom-right'; - - /* NOTIFICATION *********************************************/ - - var showNotification = function (type, message, title, options) { - toastr[type](message, title, options); - }; - - abp.notify.success = function (message, title, options) { - showNotification('success', message, title, options); - }; - - abp.notify.info = function (message, title, options) { - showNotification('info', message, title, options); - }; - - abp.notify.warn = function (message, title, options) { - showNotification('warning', message, title, options); - }; - - abp.notify.error = function (message, title, options) { - showNotification('error', message, title, options); - }; - -})(); \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Bunny/FodyWeavers.xml new file mode 100644 index 0000000000..1715698ccd --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Bunny/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.abppkg b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.abppkg new file mode 100644 index 0000000000..f4bad072d2 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.framework" +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.abppkg.analyze.json b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.abppkg.analyze.json new file mode 100644 index 0000000000..b9e8bbba1b --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.abppkg.analyze.json @@ -0,0 +1,68 @@ +{ + "name": "Volo.Abp.BlobStoring.Bunny", + "hash": "", + "contents": [ + { + "namespace": "Volo.Abp.BlobStoring.Bunny", + "dependsOnModules": [ + { + "declaringAssemblyName": "Volo.Abp.BlobStoring", + "namespace": "Volo.Abp.BlobStoring", + "name": "AbpBlobStoringModule" + }, + { + "declaringAssemblyName": "Volo.Abp.Caching", + "namespace": "Volo.Abp.Caching", + "name": "AbpCachingModule" + } + ], + "implementingInterfaces": [ + { + "name": "IAbpModule", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IAbpModule" + }, + { + "name": "IOnPreApplicationInitialization", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IOnPreApplicationInitialization" + }, + { + "name": "IOnApplicationInitialization", + "namespace": "Volo.Abp", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.IOnApplicationInitialization" + }, + { + "name": "IOnPostApplicationInitialization", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IOnPostApplicationInitialization" + }, + { + "name": "IOnApplicationShutdown", + "namespace": "Volo.Abp", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.IOnApplicationShutdown" + }, + { + "name": "IPreConfigureServices", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IPreConfigureServices" + }, + { + "name": "IPostConfigureServices", + "namespace": "Volo.Abp.Modularity", + "declaringAssemblyName": "Volo.Abp.Core", + "fullName": "Volo.Abp.Modularity.IPostConfigureServices" + } + ], + "contentType": "abpModule", + "name": "AbpBlobStoringBunnyModule", + "summary": null + } + ] +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.csproj b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.csproj new file mode 100644 index 0000000000..2cd9c74ac6 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.csproj @@ -0,0 +1,26 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + enable + Nullable + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/AbpBunnyBlobStoringModule .cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/AbpBunnyBlobStoringModule .cs new file mode 100644 index 0000000000..3fe814f2f0 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/AbpBunnyBlobStoringModule .cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Caching; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Bunny; + +[DependsOn( + typeof(AbpBlobStoringModule), + typeof(AbpCachingModule))] +public class AbpBlobStoringBunnyModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpClient(); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyApiException.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyApiException.cs new file mode 100644 index 0000000000..1e10d4caa2 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyApiException.cs @@ -0,0 +1,18 @@ +using System; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class BunnyApiException : Exception +{ + public BunnyApiException(string message) + : base(message) + { + + } + + public BunnyApiException(string message, Exception innerException) + : base(message, innerException) + { + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..03afe1c36d --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobContainerConfigurationExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Volo.Abp.BlobStoring.Bunny; + +public static class BunnyBlobContainerConfigurationExtensions +{ + public static BunnyBlobProviderConfiguration GetBunnyConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new BunnyBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseBunny( + this BlobContainerConfiguration containerConfiguration, + Action bunnyConfigureAction) + { + containerConfiguration.ProviderType = typeof(BunnyBlobProvider); + containerConfiguration.NamingNormalizers.TryAdd(); + + bunnyConfigureAction(new BunnyBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobNamingNormalizer.cs new file mode 100644 index 0000000000..74c436e2e9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobNamingNormalizer.cs @@ -0,0 +1,51 @@ +using System.Globalization; +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class BunnyBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency +{ + private readonly static Regex ValidCharactersRegex = + new Regex(@"^[a-z0-9-]*$", RegexOptions.Compiled); + + private const int MinLength = 4; + private const int MaxLength = 64; + + public virtual string NormalizeBlobName(string blobName) => blobName; + + public virtual string NormalizeContainerName(string containerName) + { + Check.NotNullOrWhiteSpace(containerName, nameof(containerName)); + + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + // Trim whitespace and convert to lowercase + var normalizedName = containerName + .Trim() + .ToLowerInvariant(); + + // Remove any invalid characters + normalizedName = Regex.Replace(normalizedName, "[^a-z0-9-]", string.Empty); + + // Validate structure + if (!ValidCharactersRegex.IsMatch(normalizedName)) + { + throw new AbpException( + $"Container name contains invalid characters: {containerName}. " + + "Only lowercase letters, numbers, and hyphens are allowed."); + } + + // Validate length + if (normalizedName.Length < MinLength || normalizedName.Length > MaxLength) + { + throw new AbpException( + $"Container name must be between {MinLength} and {MaxLength} characters. " + + $"Current length: {normalizedName.Length}"); + } + + return normalizedName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProvider.cs new file mode 100644 index 0000000000..6c06bc3f95 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProvider.cs @@ -0,0 +1,167 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using BunnyCDN.Net.Storage; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class BunnyBlobProvider : BlobProviderBase, ITransientDependency +{ + protected IBunnyBlobNameCalculator BunnyBlobNameCalculator { get; } + protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } + protected IBunnyClientFactory BunnyClientFactory { get; } + + public BunnyBlobProvider( + IBunnyBlobNameCalculator bunnyBlobNameCalculator, + IBlobNormalizeNamingService blobNormalizeNamingService, + IBunnyClientFactory bunnyClientFactory) + { + BunnyBlobNameCalculator = bunnyBlobNameCalculator; + BlobNormalizeNamingService = blobNormalizeNamingService; + BunnyClientFactory = bunnyClientFactory; + } + + public async override Task SaveAsync(BlobProviderSaveArgs args) + { + var configuration = args.Configuration.GetBunnyConfiguration(); + var containerName = GetContainerName(args); + var blobName = BunnyBlobNameCalculator.Calculate(args); + + await ValidateContainerExistsAsync(containerName, configuration); + + var bunnyStorage = await GetBunnyCDNStorageAsync(args); + + if (!args.OverrideExisting && await BlobExistsAsync(bunnyStorage, containerName, blobName)) + { + throw new BlobAlreadyExistsException( + $"Blob '{args.BlobName}' already exists in container '{containerName}'. " + + $"Set {nameof(args.OverrideExisting)} to true to overwrite."); + } + + using var memoryStream = new MemoryStream(); + await args.BlobStream.CopyToAsync(memoryStream); + memoryStream.Position = 0; + + await bunnyStorage.UploadAsync(memoryStream, $"{containerName}/{blobName}"); + } + + public async override Task DeleteAsync(BlobProviderDeleteArgs args) + { + var blobName = BunnyBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + var bunnyStorage = await GetBunnyCDNStorageAsync(args); + + if (!await BlobExistsAsync(bunnyStorage, containerName, blobName)) + { + return false; + } + + try + { + return await bunnyStorage.DeleteObjectAsync($"{containerName}/{blobName}"); + } + catch (BunnyCDNStorageException ex) when (ex.Message.Contains("404")) + { + return false; + } + } + + public async override Task ExistsAsync(BlobProviderExistsArgs args) + { + var blobName = BunnyBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + var bunnyStorage = await GetBunnyCDNStorageAsync(args); + + return await BlobExistsAsync(bunnyStorage, containerName, blobName); + } + + public async override Task GetOrNullAsync(BlobProviderGetArgs args) + { + var blobName = BunnyBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + var bunnyStorage = await GetBunnyCDNStorageAsync(args); + + if (!await BlobExistsAsync(bunnyStorage, containerName, blobName)) + { + return null; + } + + try + { + return await bunnyStorage.DownloadObjectAsStreamAsync($"{containerName}/{blobName}"); + } + catch (WebException ex) when ((HttpStatusCode)ex.Status == HttpStatusCode.NotFound) + { + return null; + } + } + + protected virtual async Task BlobExistsAsync(BunnyCDNStorage bunnyStorage, string containerName, string blobName) + { + try + { + var fullBlobPath = $"/{containerName}/{blobName}"; + var directoryPath = Path.GetDirectoryName(fullBlobPath)?.Replace('\\', '/') + "/"; + + if (string.IsNullOrWhiteSpace(directoryPath)) + { + throw new Exception("Invalid directory path generated from blob name."); + } + + var objects = await bunnyStorage.GetStorageObjectsAsync(directoryPath); + return objects?.Any(o => o.FullPath == fullBlobPath) == true; + } + catch (BunnyCDNStorageException ex) when (ex.Message.Contains("404")) + { + return false; + } + catch (Exception ex) + { + throw new Exception($"Error while checking blob existence: {ex.Message}", ex); + } + } + + protected virtual async Task GetBunnyCDNStorageAsync(BlobProviderArgs args) + { + var configuration = args.Configuration.GetBunnyConfiguration(); + var containerName = GetContainerName(args); + var region = configuration.Region ?? "de"; + + return await BunnyClientFactory.CreateAsync( + configuration.AccessKey, + containerName, + region); + } + + protected virtual string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetBunnyConfiguration(); + return configuration.ContainerName.IsNullOrWhiteSpace() + ? args.ContainerName + : BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName!); + } + + protected virtual async Task ValidateContainerExistsAsync( + string containerName, + BunnyBlobProviderConfiguration configuration + ) + { + try + { + await BunnyClientFactory.EnsureStorageZoneExistsAsync( + configuration.AccessKey, + containerName, + configuration.Region ?? "de", + configuration.CreateContainerIfNotExists); + } + catch (Exception ex) + { + throw new AbpException( + $"Failed to validate storage zone '{containerName}': {ex.Message}", + ex); + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProviderConfiguration.cs new file mode 100644 index 0000000000..0897c7f2b8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProviderConfiguration.cs @@ -0,0 +1,40 @@ +namespace Volo.Abp.BlobStoring.Bunny; + +public class BunnyBlobProviderConfiguration +{ + public string? Region { + get => _containerConfiguration.GetConfigurationOrDefault(BunnyBlobProviderConfigurationNames.Region, "de"); + set => _containerConfiguration.SetConfiguration(BunnyBlobProviderConfigurationNames.Region, value); + } + + /// + /// This name may only contain lowercase letters, numbers, and hyphens. (no spaces) + /// The name must also be between 4 and 64 characters long. + /// The name must be globaly unique + /// If this parameter is not specified, the ContainerName of the will be used. + /// + public string? ContainerName { + get => _containerConfiguration.GetConfigurationOrDefault(BunnyBlobProviderConfigurationNames.ContainerName); + set => _containerConfiguration.SetConfiguration(BunnyBlobProviderConfigurationNames.ContainerName, value); + } + + /// + /// Default value: false. + /// + public bool CreateContainerIfNotExists { + get => _containerConfiguration.GetConfigurationOrDefault(BunnyBlobProviderConfigurationNames.CreateContainerIfNotExists, false); + set => _containerConfiguration.SetConfiguration(BunnyBlobProviderConfigurationNames.CreateContainerIfNotExists, value); + } + + public string AccessKey { + get => _containerConfiguration.GetConfiguration(BunnyBlobProviderConfigurationNames.AccessKey); + set => _containerConfiguration.SetConfiguration(BunnyBlobProviderConfigurationNames.AccessKey, value); + } + + private readonly BlobContainerConfiguration _containerConfiguration; + + public BunnyBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..09d79076a6 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProviderConfigurationNames.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.BlobStoring.Bunny; + +public static class BunnyBlobProviderConfigurationNames +{ + // The primary region for the storage zone (e.g., DE, NY, etc.) + public const string Region = "Bunny.Region"; + + // The name of the storage zone + public const string ContainerName = "Bunny.ContainerName"; + + // The API access key for the bunny.net account + public const string AccessKey = "Bunny.AccessKey"; + + public const string CreateContainerIfNotExists = "Bunny.CreateContainerIfNotExists"; +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyStorageZoneModel.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyStorageZoneModel.cs new file mode 100644 index 0000000000..e718531927 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyStorageZoneModel.cs @@ -0,0 +1,17 @@ +using System; + +namespace Volo.Abp.BlobStoring.Bunny; + +[Serializable] +public class BunnyStorageZoneModel +{ + public int Id { get; set; } + + public string Password { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string? Region { get; set; } + + public bool Deleted { get; set; } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/DefaultBunnyBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/DefaultBunnyBlobNameCalculator.cs new file mode 100644 index 0000000000..f06acb8c14 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/DefaultBunnyBlobNameCalculator.cs @@ -0,0 +1,21 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class DefaultBunnyBlobNameCalculator : IBunnyBlobNameCalculator, ITransientDependency +{ + protected ICurrentTenant CurrentTenant { get; } + + public DefaultBunnyBlobNameCalculator(ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + } + + public virtual string Calculate(BlobProviderArgs args) + { + return CurrentTenant.Id == null + ? $"host/{args.BlobName}" + : $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/DefaultBunnyClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/DefaultBunnyClientFactory.cs new file mode 100644 index 0000000000..d22cff0e0a --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/DefaultBunnyClientFactory.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using BunnyCDN.Net.Storage; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Encryption; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class DefaultBunnyClientFactory : IBunnyClientFactory, ITransientDependency +{ + private readonly IDistributedCache _cache; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IStringEncryptionService _stringEncryptionService; + + private const string CacheKeyPrefix = "BunnyStorageZone:"; + private readonly static TimeSpan CacheDuration = TimeSpan.FromHours(12); + + public DefaultBunnyClientFactory( + IHttpClientFactory httpClient, + IDistributedCache cache, + IStringEncryptionService stringEncryptionService) + { + _cache = cache; + _httpClientFactory = httpClient; + _stringEncryptionService = stringEncryptionService; + } + + public virtual async Task CreateAsync(string accessKey, string containerName, string region = "de") + { + var cacheKey = $"{CacheKeyPrefix}{containerName}"; + var storageZoneInfo = await _cache.GetOrAddAsync( + cacheKey, + async () => { + var result = await GetStorageZoneAsync(accessKey, containerName); + if (result == null) + { + throw new AbpException($"Storage zone '{containerName}' not found"); + } + + // Encrypt the sensitive password before caching + result.Password = _stringEncryptionService.Encrypt(result.Password!)!; + return result; + }, + () => new DistributedCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.Add(CacheDuration) + } + ); + + if (storageZoneInfo == null) + { + throw new AbpException($"Could not retrieve storage zone information for container '{containerName}'"); + } + + // Decrypt the password before using it + var decryptedPassword = _stringEncryptionService.Decrypt(storageZoneInfo.Password); + + return new BunnyCDNStorage(containerName, decryptedPassword, region); + } + + public virtual async Task EnsureStorageZoneExistsAsync( + string accessKey, + string containerName, + string region = "de", + bool createIfNotExists = false) + { + var storageZone = await GetStorageZoneAsync(accessKey, containerName); + + if (storageZone == null) + { + if (!createIfNotExists) + { + throw new AbpException( + $"Storage zone '{containerName}' does not exist. " + + "Set createIfNotExists to true to create it automatically."); + } + + await CreateStorageZoneAsync(accessKey, containerName, region); + + // Clear the cache to force a refresh of the storage zone info + var cacheKey = $"{CacheKeyPrefix}{containerName}"; + await _cache.RemoveAsync(cacheKey); + } + } + + protected virtual async Task CreateStorageZoneAsync( + string accessKey, + string containerName, + string region) + { + using (var client = _httpClientFactory.CreateClient("BunnyApiClient")) + { + client.DefaultRequestHeaders.Add("AccessKey", accessKey); + + var payload = new Dictionary + { + { "Name", containerName }, + { "Region", region }, + { "ZoneTier", 0 } + }; + + var content = new StringContent( + JsonSerializer.Serialize(payload), + Encoding.UTF8, + "application/json"); + + var response = await client.PostAsync( + "https://api.bunny.net/storagezone", + content); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new AbpException( + $"Failed to create storage zone '{containerName}'. " + + $"Status: {response.StatusCode}, Error: {errorContent}"); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var createdZone = JsonSerializer.Deserialize(responseContent); + + if (createdZone == null) + { + throw new AbpException($"Failed to deserialize the created storage zone response for '{containerName}'"); + } + + return createdZone; + } + } + + protected virtual async Task GetStorageZoneAsync(string accessKey, string containerName) + { + using (var client = _httpClientFactory.CreateClient("BunnyApiClient")) + { + client.DefaultRequestHeaders.Add("AccessKey", accessKey); + var response = await client.GetAsync("https://api.bunny.net/storagezone"); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + var zones = JsonSerializer.Deserialize(content); + + return zones?.FirstOrDefault(x => x.Name.Equals(containerName, StringComparison.OrdinalIgnoreCase) && !x.Deleted); + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyBlobNameCalculator.cs new file mode 100644 index 0000000000..34a18aca46 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyBlobNameCalculator.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.BlobStoring.Bunny; + +public interface IBunnyBlobNameCalculator +{ + string Calculate(BlobProviderArgs args); +} diff --git a/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyClientFactory.cs new file mode 100644 index 0000000000..7e5db5ed41 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyClientFactory.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using BunnyCDN.Net.Storage; + +namespace Volo.Abp.BlobStoring.Bunny; + +public interface IBunnyClientFactory +{ + Task CreateAsync(string accessKey, string containerName, string region = "de"); + + Task EnsureStorageZoneExistsAsync(string accessKey, string containerName, string region = "de", bool createIfNotExists = false); +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs index af06d908f0..b73a56b166 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs @@ -11,7 +11,7 @@ public static class AbpModuleHelper public static List FindAllModuleTypes(Type startupModuleType, ILogger? logger) { var moduleTypes = new List(); - logger?.Log(LogLevel.Information, "Loaded ABP modules:"); + logger?.Log(LogLevel.Debug, "Loaded ABP modules:"); AddModuleAndDependenciesRecursively(moduleTypes, startupModuleType, logger); return moduleTypes; } @@ -72,7 +72,7 @@ public static class AbpModuleHelper } moduleTypes.Add(moduleType); - logger?.Log(LogLevel.Information, $"{new string(' ', depth * 2)}- {moduleType.FullName}"); + logger?.Log(LogLevel.Debug, $"{new string(' ', depth * 2)}- {moduleType.FullName}"); foreach (var dependedModuleType in FindDependedModuleTypes(moduleType)) { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs index d56660be57..63ddc222bd 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEfCoreNavigationHelper.cs @@ -31,6 +31,14 @@ public class AbpEfCoreNavigationHelper : ITransientDependency protected virtual void EntityEntryTrackedOrStateChanged(EntityEntry entityEntry) { + if (entityEntry.State is EntityState.Unchanged or EntityState.Modified) + { + foreach (var entry in EntityEntries.Values.Where(x => x.NavigationEntries.Any())) + { + entry.UpdateNavigationEntries(); + } + } + if (entityEntry.State != EntityState.Unchanged) { return; @@ -189,6 +197,22 @@ public class AbpEfCoreNavigationHelper : ITransientDependency return navigationEntryProperty != null && navigationEntryProperty.IsModified; } + public virtual AbpNavigationEntry? GetNavigationEntry(EntityEntry entityEntry, int navigationEntryIndex) + { + var entryId = GetEntityEntryIdentity(entityEntry); + if (entryId == null) + { + return null; + } + + if (!EntityEntries.TryGetValue(entryId, out var abpEntityEntry)) + { + return null; + } + + return abpEntityEntry.NavigationEntries.ElementAtOrDefault(navigationEntryIndex); + } + protected virtual string? GetEntityEntryIdentity(EntityEntry entityEntry) { if (entityEntry.Entity is IEntity entryEntity && entryEntity.GetKeys().Length == 1) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs index 0aef48e0d1..4b8a531db1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ChangeTrackers/AbpEntityEntry.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; @@ -32,6 +33,49 @@ public class AbpEntityEntry EntityEntry = entityEntry; NavigationEntries = EntityEntry.Navigations.Select(x => new AbpNavigationEntry(x, x.Metadata.Name)).ToList(); } + + public void UpdateNavigationEntries() + { + foreach (var navigationEntry in NavigationEntries) + { + if (IsModified || + EntityEntry.State == EntityState.Modified || + navigationEntry.IsModified || + navigationEntry.NavigationEntry.IsModified) + { + continue; + } + + var navigation = EntityEntry.Navigations.FirstOrDefault(n => n.Metadata.Name == navigationEntry.Name); + + var currentValue = AbpNavigationEntry.GetOriginalValue(navigation?.CurrentValue); + if (currentValue == null) + { + continue; + } + + switch (navigationEntry.OriginalValue) + { + case null: + navigationEntry.OriginalValue = currentValue; + break; + case IEnumerable originalValueCollection when currentValue is IEnumerable currentValueCollection: + { + var existingList = originalValueCollection.Cast().ToList(); + var newList = currentValueCollection.Cast().ToList(); + if (newList.Count > existingList.Count) + { + navigationEntry.OriginalValue = currentValue; + } + + break; + } + default: + navigationEntry.OriginalValue = currentValue; + break; + } + } + } } public class AbpNavigationEntry @@ -42,9 +86,29 @@ public class AbpNavigationEntry public bool IsModified { get; set; } + public List? OriginalValue { get; set; } + + public object? CurrentValue => NavigationEntry.CurrentValue; + public AbpNavigationEntry(NavigationEntry navigationEntry, string name) { NavigationEntry = navigationEntry; Name = name; + OriginalValue = GetOriginalValue(navigationEntry.CurrentValue); + } + + public static List? GetOriginalValue(object? currentValue) + { + if (currentValue is null) + { + return null; + } + + if (currentValue is IEnumerable enumerable) + { + return enumerable.Cast().ToList(); + } + + return new List { currentValue }; } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index a52ac2f437..d50e6b89f6 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -193,16 +194,20 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency } } - if (Options.SaveEntityHistoryWhenNavigationChanges && AbpEfCoreNavigationHelper != null) + if (AbpEfCoreNavigationHelper != null) { foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) { if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) { + var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); + var isCollection = navigationEntry.Metadata.IsCollection; propertyChanges.Add(new EntityPropertyChangeInfo { PropertyName = navigationEntry.Metadata.Name, - PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName! + PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!, + OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection), + NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection) }); } } @@ -211,6 +216,44 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency return propertyChanges; } + protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection) + { + switch (entity) + { + case null: + return null; + + case IEntity entryEntity: + var keys = entryEntity.GetKeys(); + return keys.Length == 0 ? null : string.Join(", ",keys).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength); + + case IEnumerable enumerable: + var keysList = new List(); + foreach (var item in enumerable) + { + var id = GetNavigationPropertyValue(item, false); + if (id != null) + { + keysList.Add(id); + } + } + + if (keysList.Count == 0) + { + return null; + } + + var serializedKeysEnumerable = keysList.Count == 1 && !isCollection + ? keysList.First() + : JsonSerializer.Serialize(keysList); + + return serializedKeysEnumerable.TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength); + + default: + return null; + } + } + protected virtual bool IsCreated(EntityEntry entityEntry) { return entityEntry.State == EntityState.Added; diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs index a687ba2bf9..acd905b8c6 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs @@ -1,8 +1,8 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; -using MongoDB.Driver.Linq; using Volo.Abp.Domain.Entities; namespace Volo.Abp.Domain.Repositories.MongoDB; @@ -20,10 +20,13 @@ public interface IMongoDbRepository : IRepository Task> GetCollectionAsync(CancellationToken cancellationToken = default); - [Obsolete("Use GetMongoQueryableAsync method.")] - IMongoQueryable GetMongoQueryable(); + [Obsolete("Use GetQueryable method.")] + IQueryable GetMongoQueryable(); - Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null); + [Obsolete("Use GetQueryableAsync method.")] + Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null); + + Task> GetQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null); Task> GetAggregateAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null); } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 45629f76e5..fdc7464d6b 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -455,19 +455,19 @@ public class MongoDbRepository public async override Task> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)).ToListAsync(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)).ToListAsync(cancellationToken); } public async override Task> GetListAsync(Expression> predicate, bool includeDetails = false, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)).Where(predicate).ToListAsync(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)).Where(predicate).ToListAsync(cancellationToken); } public async override Task GetCountAsync(CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)).LongCountAsync(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)).LongCountAsync(cancellationToken); } public async override Task> GetPagedListAsync( @@ -479,10 +479,9 @@ public class MongoDbRepository { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderByIf>(!sorting.IsNullOrWhiteSpace(), sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy>(skipCount, maxResultCount) .ToListAsync(cancellationToken); } @@ -493,14 +492,14 @@ public class MongoDbRepository { cancellationToken = GetCancellationToken(cancellationToken); - var entities = await (await GetMongoQueryableAsync(cancellationToken)) + var entities = await (await GetQueryableAsync(cancellationToken)) .Where(predicate) .ToListAsync(cancellationToken); await DeleteManyAsync(entities, autoSave, cancellationToken); } - public override async Task DeleteDirectAsync(Expression> predicate, CancellationToken cancellationToken = default) + public async override Task DeleteDirectAsync(Expression> predicate, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); @@ -527,12 +526,16 @@ public class MongoDbRepository [Obsolete("Use GetQueryableAsync method.")] protected override IQueryable GetQueryable() { - return GetMongoQueryable(); + return ApplyDataFilters( + SessionHandle != null + ? Collection.AsQueryable(SessionHandle) + : Collection.AsQueryable() + ); } public async override Task> GetQueryableAsync() { - return await GetMongoQueryableAsync(); + return await GetQueryableAsync(); } public async override Task FindAsync( @@ -542,34 +545,36 @@ public class MongoDbRepository { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(predicate) .SingleOrDefaultAsync(cancellationToken); } - [Obsolete("Use GetMongoQueryableAsync method.")] - public virtual IMongoQueryable GetMongoQueryable() + [Obsolete("Use GetQueryableAsync method.")] + public virtual IQueryable GetMongoQueryable() { - return ApplyDataFilters( - SessionHandle != null - ? Collection.AsQueryable(SessionHandle) - : Collection.AsQueryable() - ); + return GetQueryable(); + } + + [Obsolete("Use GetQueryableAsync method.")] + public virtual Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null) + { + return GetQueryableAsync(cancellationToken, options); } - public virtual Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) + public virtual async Task> GetQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? options = null) { - return GetMongoQueryableAsync(cancellationToken, aggregateOptions); + return await GetQueryableAsync(cancellationToken, options); } - protected virtual async Task> GetMongoQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) + protected virtual async Task> GetQueryableAsync(CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) { cancellationToken = GetCancellationToken(cancellationToken); var dbContext = await GetDbContextAsync(cancellationToken); var collection = dbContext.Collection(); - return ApplyDataFilters, TOtherEntity>( + return ApplyDataFilters, TOtherEntity>( dbContext.SessionHandle != null ? collection.AsQueryable(dbContext.SessionHandle, aggregateOptions) : collection.AsQueryable(aggregateOptions) @@ -810,7 +815,7 @@ public class MongoDbRepository { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Id!.Equals(id)) .FirstOrDefaultAsync(cancellationToken); } @@ -827,7 +832,7 @@ public class MongoDbRepository { cancellationToken = GetCancellationToken(cancellationToken); - var entities = await (await GetMongoQueryableAsync(cancellationToken)) + var entities = await (await GetQueryableAsync(cancellationToken)) .Where(x => ids.Contains(x.Id)) .ToListAsync(cancellationToken); diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs index 676450a5b8..353185c8e3 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs @@ -1,8 +1,8 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; -using MongoDB.Driver.Linq; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories.MongoDB; @@ -36,19 +36,26 @@ public static class MongoDbCoreRepositoryExtensions return repository.ToMongoDbRepository().GetCollectionAsync(cancellationToken); } - [Obsolete("Use GetMongoQueryableAsync method.")] - public static IMongoQueryable GetMongoQueryable(this IReadOnlyBasicRepository repository) + [Obsolete("Use GetQueryableAsync method.")] + public static IQueryable GetMongoQueryable(this IReadOnlyBasicRepository repository) where TEntity : class, IEntity { return repository.ToMongoDbRepository().GetMongoQueryable(); } - public static Task> GetMongoQueryableAsync(this IReadOnlyBasicRepository repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) + [Obsolete("Use GetQueryableAsync method.")] + public static Task> GetMongoQueryableAsync(this IReadOnlyBasicRepository repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) where TEntity : class, IEntity { return repository.ToMongoDbRepository().GetMongoQueryableAsync(cancellationToken, aggregateOptions); } + public static Task> GetQueryableAsync(this IReadOnlyBasicRepository repository) + where TEntity : class, IEntity + { + return repository.ToMongoDbRepository().GetQueryableAsync(); + } + public static Task> GetAggregateAsync(this IReadOnlyBasicRepository repository, CancellationToken cancellationToken = default, AggregateOptions? aggregateOptions = null) where TEntity : class, IEntity { diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs index c1d8b8f514..6ad2b0b359 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbDateTimeSerializer.cs @@ -5,7 +5,7 @@ using MongoDB.Bson.Serialization.Serializers; namespace Volo.Abp.MongoDB; -public class AbpMongoDbDateTimeSerializer : DateTimeSerializer +public class AbpMongoDbDateTimeSerializer : StructSerializerBase { protected DateTimeKind DateTimeKind { get; set; } protected bool DisableDateTimeNormalization { get; set; } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs index f61e9ddb45..91d2808010 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs @@ -1,5 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; using Volo.Abp.Domain; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; @@ -19,6 +22,8 @@ public class AbpMongoDbModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { + BsonSerializer.TryRegisterSerializer(new GuidSerializer(GuidRepresentation.Standard)); + context.Services.TryAddTransient( typeof(IMongoDbContextProvider<>), typeof(UnitOfWorkMongoDbContextProvider<>) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs index 6a73af27f5..9d1b706d8b 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs @@ -68,7 +68,6 @@ public class MongoDbContextEventInbox : IMongoDbContextEventInb .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) - .As>() .ToListAsync(cancellationToken: cancellationToken); return outgoingEventRecords diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs index 57b59038b5..6dc8817f02 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs @@ -56,7 +56,6 @@ public class MongoDbContextEventOutbox : IMongoDbContextEventOu .WhereIf(transformedFilter != null, transformedFilter!) .OrderBy(x => x.CreationTime) .Take(maxCount) - .As>() .ToListAsync(cancellationToken: cancellationToken); return outgoingEventRecords diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs index 287f3ced0e..b7e896a27a 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs @@ -6,9 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Linq; -using MongoDB.Driver; using MongoDB.Driver.Linq; -using Volo.Abp.DynamicProxy; namespace Volo.Abp.MongoDB; @@ -16,344 +14,339 @@ public class MongoDbAsyncQueryableProvider : IAsyncQueryableProvider, ISingleton { public bool CanExecute(IQueryable queryable) { - return ProxyHelper.UnProxy(queryable) is IMongoQueryable; - } - - protected virtual IMongoQueryable GetMongoQueryable(IQueryable queryable) - { - return ProxyHelper.UnProxy(queryable).As>(); + return queryable.Provider is IMongoQueryProvider; } public Task ContainsAsync(IQueryable queryable, T item, CancellationToken cancellationToken = default) { - return Task.FromResult(GetMongoQueryable(queryable).Contains(item)); + return Task.FromResult(queryable.Contains(item)); } public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AnyAsync(cancellationToken); + return queryable.AnyAsync(cancellationToken); } public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AnyAsync(predicate, cancellationToken); + return queryable.AnyAsync(predicate, cancellationToken); } public Task AllAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return Task.FromResult(GetMongoQueryable(queryable).All(predicate)); + return Task.FromResult(queryable.All(predicate)); } public Task CountAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).CountAsync(cancellationToken); + return queryable.CountAsync(cancellationToken); } public Task CountAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).CountAsync(predicate, cancellationToken); + return queryable.CountAsync(predicate, cancellationToken); } public Task LongCountAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).LongCountAsync(cancellationToken); + return queryable.LongCountAsync(cancellationToken); } public Task LongCountAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).LongCountAsync(predicate, cancellationToken); + return queryable.LongCountAsync(predicate, cancellationToken); } public Task FirstAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).FirstAsync(cancellationToken); + return queryable.FirstAsync(cancellationToken); } public Task FirstAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).FirstAsync(predicate, cancellationToken); + return queryable.FirstAsync(predicate, cancellationToken); } public Task FirstOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).FirstOrDefaultAsync(cancellationToken)!; + return queryable.FirstOrDefaultAsync(cancellationToken)!; } public Task FirstOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).FirstOrDefaultAsync(predicate, cancellationToken)!; + return queryable.FirstOrDefaultAsync(predicate, cancellationToken)!; } public Task LastAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return Task.FromResult(GetMongoQueryable(queryable).Last()); + return Task.FromResult(queryable.Last()); } public Task LastAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return Task.FromResult(GetMongoQueryable(queryable).Last(predicate)); + return Task.FromResult(queryable.Last(predicate)); } public Task LastOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return Task.FromResult(GetMongoQueryable(queryable).LastOrDefault()); + return Task.FromResult(queryable.LastOrDefault()); } public Task LastOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return Task.FromResult(GetMongoQueryable(queryable).LastOrDefault(predicate)); + return Task.FromResult(queryable.LastOrDefault(predicate)); } public Task SingleAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SingleAsync(cancellationToken); + return queryable.SingleAsync(cancellationToken); } public Task SingleAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SingleAsync(predicate, cancellationToken); + return queryable.SingleAsync(predicate, cancellationToken); } public Task SingleOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SingleOrDefaultAsync(cancellationToken)!; + return queryable.SingleOrDefaultAsync(cancellationToken)!; } public Task SingleOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SingleOrDefaultAsync(predicate, cancellationToken)!; + return queryable.SingleOrDefaultAsync(predicate, cancellationToken)!; } public Task MinAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).MinAsync(cancellationToken); + return queryable.MinAsync(cancellationToken); } public Task MinAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).MinAsync(selector, cancellationToken); + return queryable.MinAsync(selector, cancellationToken); } public Task MaxAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).MaxAsync(cancellationToken); + return queryable.MaxAsync(cancellationToken); } public Task MaxAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).MaxAsync(selector, cancellationToken); + return queryable.MaxAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(cancellationToken); + return queryable.SumAsync(cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).SumAsync(selector, cancellationToken); + return queryable.SumAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(cancellationToken); + return queryable.AverageAsync(cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).AverageAsync(selector, cancellationToken); + return queryable.AverageAsync(selector, cancellationToken); } public Task> ToListAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return GetMongoQueryable(queryable).ToListAsync(cancellationToken); + return queryable.ToListAsync(cancellationToken); } public async Task ToArrayAsync(IQueryable queryable, CancellationToken cancellationToken = default) { - return (await GetMongoQueryable(queryable).ToListAsync(cancellationToken)).ToArray(); + return (await queryable.ToListAsync(cancellationToken)).ToArray(); } } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 954e6222ee..aa11d7ffde 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -512,7 +513,9 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) && - x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName)); + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName && + x.EntityChanges[1].PropertyChanges[0].OriginalValue == null && + x.EntityChanges[1].PropertyChanges[0].NewValue == entityId.ToString())); AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 @@ -539,10 +542,13 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToOne) && - x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName)); + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(AppEntityWithNavigationChildOneToOne).FullName && + x.EntityChanges[1].PropertyChanges[0].OriginalValue == entityId.ToString() && + x.EntityChanges[1].PropertyChanges[0].NewValue == null)); AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + var oneToManyId = ""; using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) @@ -561,6 +567,8 @@ public class Auditing_Tests : AbpAuditingTestBase await repository.UpdateAsync(entity); await uow.CompleteAsync(); await scope.SaveAsync(); + + oneToManyId = entity.OneToMany.First().Id.ToString(); } } @@ -572,36 +580,80 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) && - x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName && + x.EntityChanges[1].PropertyChanges[0].OriginalValue == null && + x.EntityChanges[1].PropertyChanges[0].NewValue == $"[\"{oneToManyId}\"]")); AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + var newOneToManyId = ""; using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) { var entity = await repository.GetAsync(entityId); - entity.OneToMany = null; + entity.OneToMany.Add(new AppEntityWithNavigationChildOneToMany + { + AppEntityWithNavigationId = entity.Id, + ChildName = "ChildName2" + }); await repository.UpdateAsync(entity); await uow.CompleteAsync(); await scope.SaveAsync(); + + newOneToManyId = JsonSerializer.Serialize(entity.OneToMany.Select(x => x.Id).ToList()); } } #pragma warning disable 4014 AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 2 && - x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[0].ChangeType == EntityChangeType.Created && x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName && x.EntityChanges[1].ChangeType == EntityChangeType.Updated && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) && - x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName && + x.EntityChanges[1].PropertyChanges[0].OriginalValue == $"[\"{oneToManyId}\"]" && + x.EntityChanges[1].PropertyChanges[0].NewValue == newOneToManyId)); + AuditingStore.ClearReceivedCalls(); +#pragma warning restore 4014 + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + + newOneToManyId = JsonSerializer.Serialize(entity.OneToMany.Select(x => x.Id).ToList()); + + entity.OneToMany = null; + + await repository.UpdateAsync(entity); + await uow.CompleteAsync(); + await scope.SaveAsync(); + } + } + +#pragma warning disable 4014 + AuditingStore.Received().SaveAsync(Arg.Is(x => x.EntityChanges.Count == 3 && + x.EntityChanges[0].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName && + x.EntityChanges[1].ChangeType == EntityChangeType.Deleted && + x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildOneToMany).FullName && + x.EntityChanges[2].ChangeType == EntityChangeType.Updated && + x.EntityChanges[2].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && + x.EntityChanges[2].PropertyChanges.Count == 1 && + x.EntityChanges[2].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.OneToMany) && + x.EntityChanges[2].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName && + x.EntityChanges[2].PropertyChanges[0].OriginalValue == newOneToManyId && + x.EntityChanges[2].PropertyChanges[0].NewValue == null)); AuditingStore.ClearReceivedCalls(); #pragma warning restore 4014 + var manyToManyId = ""; using (var scope = _auditingManager.BeginScope()) { using (var uow = _unitOfWorkManager.Begin()) @@ -619,6 +671,8 @@ public class Auditing_Tests : AbpAuditingTestBase await repository.UpdateAsync(entity); await uow.CompleteAsync(); await scope.SaveAsync(); + + manyToManyId = entity.ManyToMany.First().Id.ToString(); } } @@ -630,7 +684,9 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigations).FullName && x.EntityChanges[1].PropertyChanges.Count == 1 && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && - x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName)); + x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName && + x.EntityChanges[1].PropertyChanges[0].OriginalValue == null && + x.EntityChanges[1].PropertyChanges[0].NewValue == $"[\"{manyToManyId}\"]")); #pragma warning restore 4014 @@ -655,6 +711,8 @@ public class Auditing_Tests : AbpAuditingTestBase x.EntityChanges[0].PropertyChanges.Count == 1 && x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigations.ManyToMany) && x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(List).FullName && + x.EntityChanges[0].PropertyChanges[0].OriginalValue == $"[\"{manyToManyId}\"]" && + x.EntityChanges[0].PropertyChanges[0].NewValue == null && x.EntityChanges[1].ChangeType == EntityChangeType.Updated && x.EntityChanges[1].EntityTypeFullName == typeof(AppEntityWithNavigationChildManyToMany).FullName && diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo.Abp.BlobStoring.Bunny.Tests.abppkg b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo.Abp.BlobStoring.Bunny.Tests.abppkg new file mode 100644 index 0000000000..a686451fbc --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo.Abp.BlobStoring.Bunny.Tests.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.test" +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo.Abp.BlobStoring.Bunny.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo.Abp.BlobStoring.Bunny.Tests.csproj new file mode 100644 index 0000000000..408f630fe2 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo.Abp.BlobStoring.Bunny.Tests.csproj @@ -0,0 +1,19 @@ + + + + + + net9.0 + + 9f0d2c00-80c1-435b-bfab-2c39c8249091 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/AbpBlobStoringBunnyTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/AbpBlobStoringBunnyTestBase.cs new file mode 100644 index 0000000000..4f37cfca91 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/AbpBlobStoringBunnyTestBase.cs @@ -0,0 +1,19 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class AbpBlobStoringBunnyTestCommonBase : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } +} + +public class AbpBlobStoringBunnyTestBase : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/AbpBlobStoringBunnyTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/AbpBlobStoringBunnyTestModule.cs new file mode 100644 index 0000000000..73066291ed --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/AbpBlobStoringBunnyTestModule.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute.Extensions; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace Volo.Abp.BlobStoring.Bunny; + +/// +/// This module will not try to connect to Bunny. +/// +[DependsOn( + typeof(AbpBlobStoringBunnyModule), + typeof(AbpBlobStoringTestModule) +)] +public class AbpBlobStoringBunnyTestCommonModule : AbpModule +{ +} + +[DependsOn( + typeof(AbpBlobStoringBunnyTestCommonModule) +)] +public class AbpBlobStoringBunnyTestModule : AbpModule +{ + private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + + private readonly string _randomContainerName = "abp-bunny-test-container-" + Guid.NewGuid().ToString("N"); + + private BunnyBlobProviderConfiguration _configuration; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + var configuration = context.Services.GetConfiguration(); + var accessKey = configuration["Bunny:AccessKey"]; + var region = configuration["Bunny:Region"]; + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseBunny(bunny => + { + bunny.AccessKey = accessKey; + bunny.Region = region; + bunny.CreateContainerIfNotExists = true; + bunny.ContainerName = _randomContainerName; + + _configuration = bunny; + }); + }); + }); + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/BunnyBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/BunnyBlobContainer_Tests.cs new file mode 100644 index 0000000000..84b255e4c5 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/BunnyBlobContainer_Tests.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.BlobStoring.Bunny; + +/* +//Please set the correct connection string in secrets.json and continue the test. +public class BunnyBlobContainer_Tests : BlobContainer_Tests +{ + public BunnyBlobContainer_Tests() + { + + } +} +*/ diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/BunnyBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/BunnyBlobNameCalculator_Tests.cs new file mode 100644 index 0000000000..fc3b2d8365 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/BunnyBlobNameCalculator_Tests.cs @@ -0,0 +1,56 @@ +using System; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class BunnyBlobNameCalculatorTests : AbpBlobStoringBunnyTestCommonBase +{ + private readonly IBunnyBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string BunnyContainerName = "/"; + private const string BunnySeparator = "/"; + + public BunnyBlobNameCalculatorTests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{BunnySeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{BunnySeparator}{tenantId:D}{BunnySeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseBunny(x => + { + x.ContainerName = containerName; + }), + blobName + ); + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/DefaultBunnyBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/DefaultBunnyBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..c4d3cd6878 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Bunny.Tests/Volo/Abp/BlobStoring/Bunny/DefaultBunnyBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,57 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.Bunny; + +public class DefaultBunnyBlobNamingNormalizerProviderTests : AbpBlobStoringBunnyTestCommonBase +{ + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultBunnyBlobNamingNormalizerProviderTests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash_Dots() + { + var filename = ",./this-i,/s-my-c,/ont,/ai+*/=!@#$n^&*er.name+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-containername"); + } + + [Fact] + public void NormalizeContainerName_Min_Length() + { + var filename = "a"; + Assert.Throws(()=> + { + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + }); + } + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var longName = new string('a', 65); // 65 characters + var exception = Assert.Throws(() => + _blobNamingNormalizer.NormalizeContainerName(longName) + ); + } + + [Fact] + public void NormalizeContainerName_Dots() + { + var filename = ".this..is.-.my.container....name."; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisis-mycontainername"); + } +} diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj b/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj index ae688f1640..e2328fdca3 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj @@ -15,10 +15,10 @@ - - - - + + + + diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs index 9b70fe2681..ff6fe1face 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.MongoDB; @@ -11,7 +11,8 @@ public class MongoDbFixture : IDisposable { MongoDbRunner = MongoRunner.Run(new MongoRunnerOptions { - UseSingleNodeReplicaSet = true + UseSingleNodeReplicaSet = true, + ReplicaSetSetupTimeout = TimeSpan.FromSeconds(30) }); } diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs index 0d5122228d..e2ad9088cb 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Basic_Tests.cs @@ -18,9 +18,9 @@ public class Repository_Basic_Tests : Repository_Basic_Tests>().ShouldNotBeNull(); - ((IMongoQueryable)(await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas")).ShouldNotBeNull(); - (await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas").As>().ShouldNotBeNull(); + (await PersonRepository.GetQueryableAsync()).ShouldNotBeNull(); + ((IQueryable)(await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas")).ShouldNotBeNull(); + (await PersonRepository.GetQueryableAsync()).Where(p => p.Name == "Douglas").ShouldNotBeNull(); } [Fact] @@ -69,9 +69,9 @@ public class Repository_Basic_Tests : Repository_Basic_Tests c.Name == "ISTANBUL").ShouldBeNull(); - (await CityRepository.GetMongoQueryableAsync()).FirstOrDefault(c => c.Name == "istanbul").ShouldBeNull(); - (await CityRepository.GetMongoQueryableAsync()).FirstOrDefault(c => c.Name == "Istanbul").ShouldNotBeNull(); + (await CityRepository.GetQueryableAsync()).FirstOrDefault(c => c.Name == "ISTANBUL").ShouldBeNull(); + (await CityRepository.GetQueryableAsync()).FirstOrDefault(c => c.Name == "istanbul").ShouldBeNull(); + (await CityRepository.GetQueryableAsync()).FirstOrDefault(c => c.Name == "Istanbul").ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).FirstOrDefault(p => p.Name == "douglas").ShouldNotBeNull(); (await PersonRepository.GetQueryableAsync()).FirstOrDefault(p => p.Name == "DOUGLAS").ShouldNotBeNull(); diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/CityRepository.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/CityRepository.cs index f8355b5466..1f0c0aa24d 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/CityRepository.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/TestApp/MongoDb/CityRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; diff --git a/latest-versions.json b/latest-versions.json index 43d4fbb9ac..7cf305971d 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,22 @@ [ + { + "version": "9.0.4", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.0.5" + } + }, + { + "version": "9.0.3", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.0.4" + } + }, { "version": "9.0.2", "releaseDate": "", diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json index fa7596e898..4071fac620 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json @@ -48,7 +48,7 @@ "ChangeType": "Change type", "ChangeTime": "Time", "NewValue": "New value", - "OriginalValue": "Original value", + "OriginalValue": "Old value", "PropertyName": "Property name", "PropertyTypeFullName": "Property Type Full Name", "Yes": "Yes", diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter.cs new file mode 100644 index 0000000000..f50aec3d92 --- /dev/null +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AuditLogging; + +public class AuditLogEntityTypeFullNameConverter : ITransientDependency +{ + public virtual string Convert(string typeFullName) + { + var genericType = Regex.Match(typeFullName, @"(.+?)`1\[\["); + if (!genericType.Success) + { + return ReplaceGenericSymbol(typeFullName); + } + + var type = Regex.Match(typeFullName, @"`1\[\[(.+?), "); + if (!type.Success) + { + return typeFullName; + } + + if (type.Groups[1].Value.Contains("System.Nullable`1[[")) + { + return genericType.Groups[1].Value + "<" + type.Groups[1].Value.Replace("System.Nullable`1[[", "") + "?>"; + } + + return genericType.Groups[1].Value.Contains("System.Nullable") + ? type.Groups[1].Value + "?" + : genericType.Groups[1].Value + "<" + ReplaceGenericSymbol(type.Groups[1].Value) + ">"; + } + + protected virtual string ReplaceGenericSymbol(string typeFullName) + { + return typeFullName.Contains("`1+") + ? typeFullName.Substring(0, typeFullName.IndexOf("[[", StringComparison.Ordinal)).Replace("`1+", ".") + : typeFullName; + } +} diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs index 94f0622714..30b0495658 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditLogInfoToAuditLogConverter.cs @@ -19,12 +19,19 @@ public class AuditLogInfoToAuditLogConverter : IAuditLogInfoToAuditLogConverter, protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; } protected IJsonSerializer JsonSerializer { get; } protected AbpExceptionHandlingOptions ExceptionHandlingOptions { get; } + protected AuditLogEntityTypeFullNameConverter AuditLogEntityTypeFullNameConverter { get; } - public AuditLogInfoToAuditLogConverter(IGuidGenerator guidGenerator, IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IJsonSerializer jsonSerializer, IOptions exceptionHandlingOptions) + public AuditLogInfoToAuditLogConverter( + IGuidGenerator guidGenerator, + IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, + IJsonSerializer jsonSerializer, + IOptions exceptionHandlingOptions, + AuditLogEntityTypeFullNameConverter auditLogEntityTypeFullNameConverter) { GuidGenerator = guidGenerator; ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter; JsonSerializer = jsonSerializer; + AuditLogEntityTypeFullNameConverter = auditLogEntityTypeFullNameConverter; ExceptionHandlingOptions = exceptionHandlingOptions.Value; } @@ -41,6 +48,15 @@ public class AuditLogInfoToAuditLogConverter : IAuditLogInfoToAuditLogConverter, } } + foreach (var entityChange in auditLogInfo.EntityChanges ?? Enumerable.Empty()) + { + entityChange.EntityTypeFullName = AuditLogEntityTypeFullNameConverter.Convert(entityChange.EntityTypeFullName); + foreach (var propertyChange in entityChange.PropertyChanges ?? Enumerable.Empty()) + { + propertyChange.PropertyTypeFullName = AuditLogEntityTypeFullNameConverter.Convert(propertyChange.PropertyTypeFullName); + } + } + var entityChanges = auditLogInfo .EntityChanges? .Select(entityChangeInfo => new EntityChange(GuidGenerator, auditLogId, entityChangeInfo, tenantId: auditLogInfo.TenantId)) diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs index b406e83d5c..92416174ae 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs @@ -64,8 +64,7 @@ public class MongoAuditLogRepository : MongoDbRepository>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -104,8 +103,7 @@ public class MongoAuditLogRepository : MongoDbRepository>() - .LongCountAsync(GetCancellationToken(cancellationToken)); + var count = await query.LongCountAsync(GetCancellationToken(cancellationToken)); return count; } @@ -128,7 +126,7 @@ public class MongoAuditLogRepository : MongoDbRepository auditLog.ExecutionTime >= startTime) .WhereIf(endTime.HasValue, auditLog => auditLog.ExecutionTime <= endTime) .WhereIf(hasException.HasValue && hasException.Value, auditLog => auditLog.Exceptions != null && auditLog.Exceptions != "") @@ -151,7 +149,7 @@ public class MongoAuditLogRepository : MongoDbRepository a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate) .OrderBy(t => t.ExecutionTime) .GroupBy(t => new { @@ -169,7 +167,7 @@ public class MongoAuditLogRepository : MongoDbRepository x.EntityChanges.Any(y => y.Id == entityChangeId)) .OrderBy(x => x.Id) .FirstAsync(GetCancellationToken(cancellationToken))).EntityChanges.FirstOrDefault(x => x.Id == entityChangeId); @@ -199,8 +197,7 @@ public class MongoAuditLogRepository : MongoDbRepository>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -215,7 +212,7 @@ public class MongoAuditLogRepository : MongoDbRepository>().LongCountAsync(GetCancellationToken(cancellationToken)); + var count = await query.LongCountAsync(GetCancellationToken(cancellationToken)); return count; } @@ -224,7 +221,7 @@ public class MongoAuditLogRepository : MongoDbRepository x.EntityChanges.Any(y => y.Id == entityChangeId)) .FirstAsync(GetCancellationToken(cancellationToken)); @@ -240,9 +237,8 @@ public class MongoAuditLogRepository : MongoDbRepository x.EntityChanges.Any(y => y.EntityId == entityId && y.EntityTypeFullName == entityTypeFullName)) - .As>() .OrderByDescending(x => x.ExecutionTime) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -263,7 +259,7 @@ public class MongoAuditLogRepository : MongoDbRepository x.EntityChanges) .WhereIf(auditLogId.HasValue, e => e.Id == auditLogId) .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime) diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AuditLogEntityTypeFullNameConverter_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AuditLogEntityTypeFullNameConverter_Tests.cs new file mode 100644 index 0000000000..db57347815 --- /dev/null +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AuditLogEntityTypeFullNameConverter_Tests.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AuditLogging.EntityFrameworkCore; + +public class AuditLogEntityTypeFullNameConverter_Tests : AuditLogEntityTypeFullNameConverter_Tests +{ + +} diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.csproj b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.csproj index 41718fda4e..a33c1c74f8 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.csproj +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo.Abp.AuditLogging.MongoDB.Tests.csproj @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogEntityTypeFullNameConverter_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogEntityTypeFullNameConverter_Tests.cs new file mode 100644 index 0000000000..5c803a2a44 --- /dev/null +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AuditLogEntityTypeFullNameConverter_Tests.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Volo.Abp.AuditLogging.MongoDB; + +[Collection(MongoTestCollection.Name)] +public class AuditLogEntityTypeFullNameConverter_Tests : AuditLogEntityTypeFullNameConverter_Tests +{ + +} diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs index a68f246c78..569bf3ad2d 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.AuditLogging.MongoDB; diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter_Tests.cs new file mode 100644 index 0000000000..2aac19cd42 --- /dev/null +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogEntityTypeFullNameConverter_Tests.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.AuditLogging; + +public abstract class AuditLogEntityTypeFullNameConverter_Tests : AuditLoggingTestBase + where TStartupModule : IAbpModule +{ + private readonly AuditLogEntityTypeFullNameConverter _typeFullNameConverter; + + protected AuditLogEntityTypeFullNameConverter_Tests() + { + _typeFullNameConverter = GetRequiredService(); + } + + [Fact] + public void AuditLogEntityTypeFullNameConverter_Test() + { + _typeFullNameConverter.Convert("MyType").ShouldBe("MyType"); + + _typeFullNameConverter.Convert(typeof(string).FullName!).ShouldBe("System.String"); + _typeFullNameConverter.Convert(typeof(Guid).FullName!).ShouldBe("System.Guid"); + _typeFullNameConverter.Convert(typeof(Guid?).FullName!).ShouldBe("System.Guid?"); + _typeFullNameConverter.Convert(typeof(int).FullName!).ShouldBe("System.Int32"); + _typeFullNameConverter.Convert(typeof(long?).FullName!).ShouldBe("System.Int64?"); + _typeFullNameConverter.Convert(typeof(MyClass).FullName!).ShouldBe("Volo.Abp.AuditLogging.AuditLogEntityTypeFullNameConverter_Tests.MyClass"); + + _typeFullNameConverter.Convert(typeof(ICollection).FullName!).ShouldBe($"System.Collections.Generic.ICollection"); + _typeFullNameConverter.Convert(typeof(Collection).FullName!).ShouldBe($"System.Collections.ObjectModel.Collection"); + _typeFullNameConverter.Convert(typeof(List).FullName!).ShouldBe($"System.Collections.Generic.List"); + _typeFullNameConverter.Convert(typeof(List).FullName!).ShouldBe($"System.Collections.Generic.List"); + + _typeFullNameConverter.Convert(typeof(ICollection).FullName!).ShouldBe($"System.Collections.Generic.ICollection"); + _typeFullNameConverter.Convert(typeof(Collection).FullName!).ShouldBe($"System.Collections.ObjectModel.Collection"); + _typeFullNameConverter.Convert(typeof(List).FullName!).ShouldBe($"System.Collections.Generic.List"); + } + + public class MyClass + { + + } +} diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs index 5993fdf9ba..a3f46643fe 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/MongoBackgroundJobRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; @@ -29,10 +30,10 @@ public class MongoBackgroundJobRepository : MongoDbRepository> GetWaitingListQuery(int maxResultCount, CancellationToken cancellationToken = default) + protected virtual async Task> GetWaitingListQuery(int maxResultCount, CancellationToken cancellationToken = default) { var now = Clock.Now; - return (await GetMongoQueryableAsync(cancellationToken)) + return (await GetQueryableAsync(cancellationToken)) .Where(t => !t.IsAbandoned && t.NextTryTime <= now) .OrderByDescending(t => t.Priority) .ThenBy(t => t.TryCount) diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj index c6a4b35a66..a3f84bb18a 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs index 97bd35c6fa..b41e255cb8 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.BackgroundJobs.MongoDB; diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs index 4610043405..6b2663013d 100644 --- a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/MongoDbDatabaseBlobRepository.cs @@ -17,7 +17,7 @@ public class MongoDbDatabaseBlobRepository : MongoDbRepository x.ContainerId == containerId && x.Name == name, cancellationToken @@ -28,7 +28,7 @@ public class MongoDbDatabaseBlobRepository : MongoDbRepository x.ContainerId == containerId && x.Name == name, cancellationToken diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs index 8952236f43..7c086e0bb4 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.BlobStoring.Database.MongoDB; diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj index 1bd05f4357..61c0d81d02 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/Volo.Abp.BlobStoring.Database.MongoDB.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs index 42c8bb467a..c15ef4d0cb 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Blogs/MongoBlogRepository.cs @@ -16,7 +16,7 @@ namespace Volo.Blogging.Blogs public virtual async Task FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken)); } } } diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs index 89e5b6b575..77c443e485 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Comments/MongoCommentRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; @@ -18,7 +19,7 @@ namespace Volo.Blogging.Comments public virtual async Task> GetListOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(a => a.PostId == postId) .OrderBy(a => a.CreationTime) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -26,19 +27,19 @@ namespace Volo.Blogging.Comments public virtual async Task GetCommentCountOfPostAsync(Guid postId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .CountAsync(a => a.PostId == postId, GetCancellationToken(cancellationToken)); } public virtual async Task> GetRepliesOfComment(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(a => a.RepliedCommentId == id).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default) { - var recordsToDelete = (await GetMongoQueryableAsync(cancellationToken)).Where(pt => pt.PostId == id); + var recordsToDelete = (await GetQueryableAsync(cancellationToken)).Where(pt => pt.PostId == id); foreach (var record in recordsToDelete) { diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs index 3cdcccc5fd..b3083cbd5f 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Posts/MongoPostRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver; using MongoDB.Driver.Linq; using Nito.AsyncEx; @@ -20,13 +21,13 @@ namespace Volo.Blogging.Posts public virtual async Task> GetPostsByBlogId(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(p => p.BlogId == id).OrderByDescending(p => p.CreationTime).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task IsPostUrlInUseAsync(Guid blogId, string url, Guid? excludingPostId = null, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(cancellationToken)).Where(p => blogId == p.BlogId && p.Url == url); + var query = (await GetQueryableAsync(cancellationToken)).Where(p => blogId == p.BlogId && p.Url == url); if (excludingPostId != null) { @@ -38,7 +39,7 @@ namespace Volo.Blogging.Posts public virtual async Task GetPostByUrl(Guid blogId, string url, CancellationToken cancellationToken = default) { - var post = await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); + var post = await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.BlogId == blogId && p.Url == url, GetCancellationToken(cancellationToken)); if (post == null) { @@ -50,7 +51,7 @@ namespace Volo.Blogging.Posts public virtual async Task> GetOrderedList(Guid blogId, bool @descending = false, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId); + var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId); if (!descending) { @@ -62,7 +63,7 @@ namespace Volo.Blogging.Posts public virtual async Task> GetListByUserIdAsync(Guid userId, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.CreatorId == userId) + var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.CreatorId == userId) .OrderByDescending(x => x.CreationTime); return await query.ToListAsync(GetCancellationToken(cancellationToken)); @@ -70,7 +71,7 @@ namespace Volo.Blogging.Posts public virtual async Task> GetLatestBlogPostsAsync(Guid blogId, int count, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId) + var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.BlogId == blogId) .OrderByDescending(x => x.CreationTime) .Take(count); diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs index bfa0c9fb4d..2e60faaccc 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Tagging/MongoTagRepository.cs @@ -20,27 +20,27 @@ namespace Volo.Blogging.Tagging public virtual async Task> GetListAsync(Guid blogId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId && t.Name == name).FirstAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId && t.Name == name).FirstAsync(GetCancellationToken(cancellationToken)); } public virtual async Task FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId && t.Name == name).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(t => t.BlogId == blogId && t.Name == name).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(t => ids.Contains(t.Id)).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(t => ids.Contains(t.Id)).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task DecreaseUsageCountOfTagsAsync(List ids, CancellationToken cancellationToken = default) { - var tags = await (await GetMongoQueryableAsync(cancellationToken)) + var tags = await (await GetQueryableAsync(cancellationToken)) .Where(t => ids.Contains(t.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); diff --git a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs index 3595620f54..b68e77c9fd 100644 --- a/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs +++ b/modules/blogging/src/Volo.Blogging.MongoDB/Volo/Blogging/Users/MongoBlogUserRepository.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; using Volo.Abp.MongoDB; using Volo.Abp.Users.MongoDB; using Volo.Blogging.MongoDB; @@ -17,7 +18,7 @@ namespace Volo.Blogging.Users public virtual async Task> GetUsersAsync(int maxCount, string filter, CancellationToken cancellationToken = default) { - var query = await GetMongoQueryableAsync(cancellationToken); + var query = await GetQueryableAsync(cancellationToken); if (!string.IsNullOrWhiteSpace(filter)) { diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo.Blogging.MongoDB.Tests.csproj b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo.Blogging.MongoDB.Tests.csproj index 88cd171a55..7dcac50e69 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo.Blogging.MongoDB.Tests.csproj +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo.Blogging.MongoDB.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs index ad1fce80e5..e1a4d00ce3 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; public class MongoDbFixture : IDisposable { diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogFeatureRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogFeatureRepository.cs index 334debe8c8..606c3e28ff 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogFeatureRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogFeatureRepository.cs @@ -25,14 +25,14 @@ public class MongoBlogFeatureRepository : MongoDbRepository> GetListAsync(Guid blogId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.BlogId == blogId) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetListAsync(Guid blogId, List featureNames, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.BlogId == blogId && featureNames.Contains(x.FeatureName)) .ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs index 0212e5975c..b47abe1115 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs @@ -44,7 +44,7 @@ public class MongoBlogPostRepository : MongoDbRepository(token)).FirstOrDefaultAsync(x => x.Id == blogPost.AuthorId, token); + blogPost.Author = await (await GetQueryableAsync(token)).FirstOrDefaultAsync(x => x.Id == blogPost.AuthorId, token); return blogPost; } @@ -64,13 +64,13 @@ public class MongoBlogPostRepository : MongoDbRepository>(tagFilteredEntityIds.Any(), x => tagFilteredEntityIds.Contains(x.Id)) - .WhereIf>(favoriteUserFilteredEntityIds.Any(), x => favoriteUserFilteredEntityIds.Contains(x.Id)) - .WhereIf>(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) - .WhereIf>(blogId.HasValue, x => x.BlogId == blogId) - .WhereIf>(authorId.HasValue, x => x.AuthorId == authorId) - .WhereIf>(statusFilter.HasValue, x => x.Status == statusFilter) + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf(tagFilteredEntityIds.Any(), x => tagFilteredEntityIds.Contains(x.Id)) + .WhereIf(favoriteUserFilteredEntityIds.Any(), x => favoriteUserFilteredEntityIds.Contains(x.Id)) + .WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter)) + .WhereIf(blogId.HasValue, x => x.BlogId == blogId) + .WhereIf(authorId.HasValue, x => x.AuthorId == authorId) + .WhereIf(statusFilter.HasValue, x => x.Status == statusFilter) .CountAsync(cancellationToken); } @@ -174,7 +174,7 @@ public class MongoBlogPostRepository : MongoDbRepository x.BlogId == blogId && x.Slug.ToLower() == slug, cancellationToken); } @@ -225,14 +225,14 @@ public class MongoBlogPostRepository : MongoDbRepository x.Status == BlogPostStatus.WaitingForReview, cancellationToken); } public async Task UpdateBlogAsync(Guid sourceBlogId, Guid? targetBlogId, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - var blogPosts = await (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.BlogId == sourceBlogId).ToListAsync(cancellationToken); + var blogPosts = await (await GetQueryableAsync(cancellationToken)).Where(x => x.BlogId == sourceBlogId).ToListAsync(cancellationToken); if (targetBlogId.HasValue) { foreach (var blogPost in blogPosts) diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs index 98567899e5..c25bc688b7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs @@ -23,13 +23,13 @@ public class MongoBlogRepository : MongoDbRepository ExistsAsync(Guid id, CancellationToken cancellationToken = default) { var token = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(token)).AnyAsync(x => x.Id == id, token); + return await (await GetQueryableAsync(token)).AnyAsync(x => x.Id == id, token); } public virtual async Task SlugExistsAsync(string slug, CancellationToken cancellationToken = default) { var token = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(token)).AnyAsync(x => x.Slug == slug, token); + return await (await GetQueryableAsync(token)).AnyAsync(x => x.Slug == slug, token); } public virtual async Task> GetListAsync( @@ -44,8 +44,7 @@ public class MongoBlogRepository : MongoDbRepository>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(token); } @@ -63,7 +62,7 @@ public class MongoBlogRepository : MongoDbRepository x.Id).ToList(); - var blogPostCount = await (await GetMongoQueryableAsync(token)) + var blogPostCount = await (await GetQueryableAsync(token)) .Where(blogPost => blogIds.Contains(blogPost.Id)) .GroupBy(blogPost => blogPost.BlogId) .Select(x => new @@ -82,7 +81,7 @@ public class MongoBlogRepository : MongoDbRepository>().LongCountAsync(token); + return await query.LongCountAsync(token); } public virtual Task GetBySlugAsync([NotNull] string slug, CancellationToken cancellationToken = default) @@ -93,7 +92,7 @@ public class MongoBlogRepository : MongoDbRepository> GetListQueryAsync(string filter = null, CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync(cancellationToken)) + return (await GetQueryableAsync(cancellationToken)) .WhereIf(!filter.IsNullOrWhiteSpace(), b => b.Name.Contains(filter)); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs index fab729fcd2..5b4566ecb8 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs @@ -61,19 +61,18 @@ public class MongoCommentRepository : MongoDbRepository>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(token); var commentIds = comments.Select(x => x.Id).ToList(); - var authorsQuery = from comment in (await GetMongoQueryableAsync(token)) + var authorsQuery = from comment in (await GetQueryableAsync(token)) join user in (await GetDbContextAsync(token)).CmsUsers on comment.CreatorId equals user.Id where commentIds.Contains(comment.Id) orderby comment.CreationTime select user; - var authors = await ApplyDataFilters, CmsUser>(authorsQuery).ToListAsync(token); + var authors = await ApplyDataFilters, CmsUser>(authorsQuery).ToListAsync(token); return comments .Select( @@ -104,8 +103,7 @@ public class MongoCommentRepository : MongoDbRepository>() - .LongCountAsync(GetCancellationToken(cancellationToken)); + return await query.LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetListWithAuthorsAsync( @@ -117,15 +115,15 @@ public class MongoCommentRepository : MongoDbRepository, CmsUser>(authorsQuery).ToListAsync(GetCancellationToken(cancellationToken)); + var authors = await ApplyDataFilters, CmsUser>(authorsQuery).ToListAsync(GetCancellationToken(cancellationToken)); - var commentsQuery = (await GetMongoQueryableAsync(cancellationToken)) + var commentsQuery = (await GetQueryableAsync(cancellationToken)) .Where(c => c.EntityId == entityId && c.EntityType == entityType); commentsQuery = commentApproveState switch { @@ -152,7 +150,7 @@ public class MongoCommentRepository : MongoDbRepository x.RepliedCommentId == comment.Id) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -172,7 +170,7 @@ public class MongoCommentRepository : MongoDbRepository ExistsAsync(string idempotencyToken, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .AnyAsync(x => x.IdempotencyToken == idempotencyToken, GetCancellationToken(cancellationToken)); } @@ -187,11 +185,11 @@ public class MongoCommentRepository : MongoDbRepository(cancellationToken)).FirstOrDefaultAsync(x => x.UserName == authorUsername, cancellationToken: cancellationToken); + var author = await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.UserName == authorUsername, cancellationToken: cancellationToken); var authorId = author?.Id ?? Guid.Empty; diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/MarkedItems/MongoUserMarkedItemRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/MarkedItems/MongoUserMarkedItemRepository.cs index 91fbbf996e..ea81fe03ff 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/MarkedItems/MongoUserMarkedItemRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/MarkedItems/MongoUserMarkedItemRepository.cs @@ -24,7 +24,7 @@ public class MongoUserMarkedItemRepository : MongoDbRepository x.CreatorId == userId && x.EntityType == entityType && @@ -39,7 +39,7 @@ public class MongoUserMarkedItemRepository : MongoDbRepository x.CreatorId == userId && x.EntityType == entityType) @@ -49,7 +49,7 @@ public class MongoUserMarkedItemRepository : MongoDbRepository> GetEntityIdsFilteredByUserAsync([NotNull] Guid userId, [NotNull] string entityType, [CanBeNull] Guid? tenantId = null, CancellationToken cancellationToken = default) { var dbContext = await GetDbContextAsync(); - var userMarkedItemQueryable = await GetMongoQueryableAsync(GetCancellationToken(cancellationToken)); + var userMarkedItemQueryable = await GetQueryableAsync(GetCancellationToken(cancellationToken)); var resultQueryable = userMarkedItemQueryable .Where(x => x.CreatorId == userId diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs index 1b49ac48bb..7c2f9a3891 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs @@ -26,8 +26,8 @@ public class MongoPageRepository : MongoDbRepository>( + return await (await GetQueryableAsync(cancellation)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.Title.ToLower().Contains(filter.ToLower()) || u.Slug.Contains(filter) @@ -43,13 +43,12 @@ public class MongoPageRepository : MongoDbRepository>( + return await (await GetQueryableAsync(cancellation)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.Title.ToLower().Contains(filter) || u.Slug.Contains(filter)) .OrderBy(sorting.IsNullOrEmpty() ? nameof(Page.Title) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(cancellation); } @@ -68,7 +67,7 @@ public class MongoPageRepository : MongoDbRepository ExistsAsync([NotNull] string slug, CancellationToken cancellationToken = default) { Check.NotNullOrEmpty(slug, nameof(slug)); - return await (await GetMongoQueryableAsync(cancellationToken)).AnyAsync(x => x.Slug == slug, + return await (await GetQueryableAsync(cancellationToken)).AnyAsync(x => x.Slug == slug, GetCancellationToken(cancellationToken)); } @@ -79,7 +78,7 @@ public class MongoPageRepository : MongoDbRepository FindTitleAsync(Guid pageId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.Id == pageId).Select(x => x.Title) + return await (await GetQueryableAsync(cancellationToken)).Where(x => x.Id == pageId).Select(x => x.Title) .FirstOrDefaultAsync(cancellationToken); } } diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs index 3a18f31719..d93cad27be 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Ratings/MongoRatingRepository.cs @@ -25,7 +25,7 @@ public class MongoRatingRepository : MongoDbRepository r.EntityType == entityType && r.EntityId == entityId && r.CreatorId == userId, GetCancellationToken(cancellationToken)); @@ -39,7 +39,7 @@ public class MongoRatingRepository : MongoDbRepository x.CreatorId == userId && x.EntityType == entityType && @@ -47,7 +47,7 @@ public class MongoUserReactionRepository : MongoDbRepository x.CreatorId == userId && x.EntityType == entityType && @@ -63,7 +63,7 @@ public class MongoUserReactionRepository : MongoDbRepository x.EntityType == entityType && x.EntityId == entityId) diff --git a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs index 2e8dc96d01..1ce2319a49 100644 --- a/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs +++ b/modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Tags/MongoEntityTagRepository.cs @@ -67,7 +67,7 @@ public class MongoEntityTagRepository : MongoDbRepository x.EntityType == entityType && x.Name == name, @@ -69,12 +69,12 @@ public class MongoTagRepository : MongoDbRepository(cancellationToken)) + var entityTagIds = await (await GetQueryableAsync(cancellationToken)) .Where(q => q.EntityId == entityId) .Select(q => q.TagId) .ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); - var query = (await GetMongoQueryableAsync(cancellationToken)) + var query = (await GetQueryableAsync(cancellationToken)) .Where(x => x.EntityType == entityType && entityTagIds.Contains(x.Id)); @@ -86,14 +86,14 @@ public class MongoTagRepository : MongoDbRepository> GetPopularTagsAsync(string entityType, int maxCount, CancellationToken cancellationToken = default) { - var tags = await (await GetMongoQueryableAsync(cancellationToken)) + var tags = await (await GetQueryableAsync(cancellationToken)) .Where(x => x.EntityType == entityType) .Select(x => new { x.Id, x.Name }) .ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); var tagIds = tags.Select(x => x.Id); - var entityTagCounts = await (await GetMongoQueryableAsync(cancellationToken)) + var entityTagCounts = await (await GetQueryableAsync(cancellationToken)) .Where(q => tagIds.Contains(q.TagId)) .GroupBy(q => q.TagId) .Select(q => new { TagId = q.Key, Count = q.Count() }) @@ -116,8 +116,7 @@ public class MongoTagRepository : MongoDbRepository>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -126,9 +125,9 @@ public class MongoTagRepository : MongoDbRepository> GetQueryableByFilterAsync(string filter, CancellationToken cancellationToken = default) + private async Task> GetQueryableByFilterAsync(string filter, CancellationToken cancellationToken = default) { - var mongoQueryable = await GetMongoQueryableAsync(cancellationToken: cancellationToken); + var mongoQueryable = await GetQueryableAsync(cancellationToken: cancellationToken); if (!filter.IsNullOrWhiteSpace()) { diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/MongoDbFixture.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/MongoDbFixture.cs index 22f63a3780..53c9457a30 100644 --- a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/MongoDbFixture.cs +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.CmsKit.MongoDB; diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj index 17c6dfe717..9c8dc44660 100644 --- a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs index f19b9f41df..015f711850 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/NavigationNode.cs @@ -21,6 +21,9 @@ namespace Volo.Docs.Documents [JsonPropertyName("isIndex")] public bool IsIndex { get; set; } + + [JsonPropertyName("keywords")] + public string[] Keywords { get; set; } public bool IsLeaf => !HasChildItems; diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs index 7f55ef63b0..081e920cff 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Documents/MongoDocumentRepository.cs @@ -22,7 +22,7 @@ namespace Volo.Docs.Documents public virtual async Task> GetListWithoutDetailsByProjectId(Guid projectId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(d => d.ProjectId == projectId) .Select(x => new DocumentWithoutDetails { @@ -37,7 +37,7 @@ namespace Volo.Docs.Documents public virtual async Task> GetUniqueListDocumentInfoAsync(CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Select(x=> new DocumentInfo { ProjectId = x.ProjectId, Version = x.Version, @@ -51,13 +51,13 @@ namespace Volo.Docs.Documents public virtual async Task> GetListByProjectId(Guid projectId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(d => d.ProjectId == projectId).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(d => d.ProjectId == projectId).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetUniqueDocumentsByProjectIdPagedAsync(Guid projectId, int skipCount, int maxResultCount, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(d => d.ProjectId == projectId) .OrderBy(x => x.LastCachedTime) .GroupBy(x => new { x.Name, x.LanguageCode, x.Version }) @@ -69,7 +69,7 @@ namespace Volo.Docs.Documents public virtual async Task GetUniqueDocumentCountByProjectIdAsync(Guid projectId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(d => d.ProjectId == projectId) + return await (await GetQueryableAsync(cancellationToken)).Where(d => d.ProjectId == projectId) .GroupBy(x => new { x.Name, x.LanguageCode, x.Version }) .LongCountAsync(GetCancellationToken(cancellationToken)); } @@ -89,17 +89,18 @@ namespace Volo.Docs.Documents bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.ProjectId == projectId && - x.Name == name && - x.LanguageCode == languageCode && - x.Version == version, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => + x.ProjectId == projectId && + x.Name == name && + x.LanguageCode == languageCode && + x.Version == version, GetCancellationToken(cancellationToken)); } - + public virtual async Task FindAsync(Guid projectId, List possibleNames, string languageCode, string version, bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.ProjectId == projectId && + return await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.ProjectId == projectId && possibleNames.Contains(x.Name) && x.LanguageCode == languageCode && x.Version == version, GetCancellationToken(cancellationToken)); @@ -114,11 +115,10 @@ namespace Volo.Docs.Documents public virtual async Task> GetListAsync(Guid? projectId, string version, string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(version != null, x => x.Version == version) .WhereIf(name != null, x => x.Name == name) .WhereIf(projectId.HasValue, x => x.ProjectId == projectId) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -142,26 +142,25 @@ namespace Volo.Docs.Documents int skipCount = 0, CancellationToken cancellationToken = default) { - return await (await ApplyFilterForGetAll( - await GetMongoQueryableAsync(cancellationToken), - projectId: projectId, - name: name, - version: version, - languageCode: languageCode, - fileName: fileName, - format: format, - creationTimeMin: creationTimeMin, - creationTimeMax: creationTimeMax, - lastUpdatedTimeMin: lastUpdatedTimeMin, - lastUpdatedTimeMax: lastUpdatedTimeMax, - lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin, - lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax, - lastCachedTimeMin: lastCachedTimeMin, lastCachedTimeMax: lastCachedTimeMax)) - .OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As>() - .PageBy>(skipCount, maxResultCount) - .ToListAsync(GetCancellationToken(cancellationToken)); + await GetQueryableAsync(cancellationToken), + projectId: projectId, + name: name, + version: version, + languageCode: languageCode, + fileName: fileName, + format: format, + creationTimeMin: creationTimeMin, + creationTimeMax: creationTimeMax, + lastUpdatedTimeMin: lastUpdatedTimeMin, + lastUpdatedTimeMax: lastUpdatedTimeMax, + lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin, + lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax, + lastCachedTimeMin: lastCachedTimeMin, lastCachedTimeMax: lastCachedTimeMax, cancellationToken: cancellationToken)) + .OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetAllCountAsync( @@ -184,36 +183,35 @@ namespace Volo.Docs.Documents int skipCount = 0, CancellationToken cancellationToken = default) { - - return await (await ApplyFilterForGetAll( - await GetMongoQueryableAsync(cancellationToken), - projectId: projectId, - name: name, - version: version, - languageCode: languageCode, - fileName: fileName, - format: format, - creationTimeMin: creationTimeMin, - creationTimeMax: creationTimeMax, - lastUpdatedTimeMin: lastUpdatedTimeMin, - lastUpdatedTimeMax: lastUpdatedTimeMax, - lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin, - lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax, - lastCachedTimeMin: lastCachedTimeMin, lastCachedTimeMax: lastCachedTimeMax)) - .OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As>() - .PageBy>(skipCount, maxResultCount) - .LongCountAsync(GetCancellationToken(cancellationToken)); + await GetQueryableAsync(cancellationToken), + projectId: projectId, + name: name, + version: version, + languageCode: languageCode, + fileName: fileName, + format: format, + creationTimeMin: creationTimeMin, + creationTimeMax: creationTimeMax, + lastUpdatedTimeMin: lastUpdatedTimeMin, + lastUpdatedTimeMax: lastUpdatedTimeMax, + lastSignificantUpdateTimeMin: lastSignificantUpdateTimeMin, + lastSignificantUpdateTimeMax: lastSignificantUpdateTimeMax, + lastCachedTimeMin: lastCachedTimeMin, lastCachedTimeMax: lastCachedTimeMax, + cancellationToken: cancellationToken)) + .OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting) + .PageBy(skipCount, maxResultCount) + .LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetAsync(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.Id == id).SingleAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(x => x.Id == id).SingleAsync(GetCancellationToken(cancellationToken)); } - - protected virtual async Task> ApplyFilterForGetAll( - IMongoQueryable query, + + protected virtual async Task> ApplyFilterForGetAll( + IQueryable query, Guid? projectId, string name, string version, @@ -254,7 +252,7 @@ namespace Volo.Docs.Documents { query = query.Where(d => d.FileName != null && d.FileName.Contains(fileName)); } - + if (format != null) { query = query.Where(d => d.Format != null && d.Format == format); diff --git a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs index 3960b07eb3..af54bd6bc5 100644 --- a/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs +++ b/modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs @@ -22,8 +22,7 @@ namespace Volo.Docs.Projects public virtual async Task> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default) { - var projects = await (await GetMongoQueryableAsync(cancellationToken)).OrderBy(sorting.IsNullOrEmpty() ? "Id desc" : sorting).As>() - .PageBy>(skipCount, maxResultCount) + var projects = await (await GetQueryableAsync(cancellationToken)).OrderBy(sorting.IsNullOrEmpty() ? "Id desc" : sorting).PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); return projects; @@ -31,7 +30,7 @@ namespace Volo.Docs.Projects public virtual async Task> GetListWithoutDetailsAsync(CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Select(x=> new ProjectWithoutDetails { Id = x.Id, Name = x.Name, @@ -44,7 +43,7 @@ namespace Volo.Docs.Projects { var normalizeShortName = NormalizeShortName(shortName); - var project = await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); + var project = await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(p => p.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); if (project == null) { @@ -58,7 +57,7 @@ namespace Volo.Docs.Projects { var normalizeShortName = NormalizeShortName(shortName); - return await (await GetMongoQueryableAsync(cancellationToken)).AnyAsync(x => x.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).AnyAsync(x => x.ShortName == normalizeShortName, GetCancellationToken(cancellationToken)); } private string NormalizeShortName(string shortName) diff --git a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs index e6c75d0161..3aa9b85777 100644 --- a/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs +++ b/modules/docs/src/Volo.Docs.Web/Areas/Documents/TagHelpers/TreeTagHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.AspNetCore.Razor.TagHelpers; @@ -23,7 +24,7 @@ namespace Volo.Docs.Areas.Documents.TagHelpers private const string LiItemTemplateWithLink = @"
  • {2}{3}
  • "; - private const string ListItemAnchor = @"{2}"; + private const string ListItemAnchor = @"{3}"; private const string ListItemSpan = @"{1}"; @@ -155,7 +156,8 @@ namespace Volo.Docs.Areas.Documents.TagHelpers sb.Clear(); - listInnerItem = string.Format(ListItemAnchor, NormalizePath(node.Path), textCss, + var additionalAttributes = node.Keywords.IsNullOrEmpty() ? "" : "data-keywords=\"" + node.Keywords.JoinAsString(",") + "\""; + listInnerItem = string.Format(ListItemAnchor, NormalizePath(node.Path), additionalAttributes ,textCss, node.Text.IsNullOrEmpty() ? "?" : sb.Append(node.Text).Append(badgeStringBuilder.ToString()).ToString()); diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js index 6636787f33..23eca169b5 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js @@ -29,7 +29,8 @@ var doc = doc || {}; var $ul = $(``); var $li = $(`
  • `); - $li.append(` ${node.text}`) + var dataKeywords = node.keywords ? `data-keywords="${node.keywords}"` : ""; + $li.append(` ${node.text}`) if(node.isLazyExpandable){ $li.addClass("lazy-expand"); @@ -111,12 +112,17 @@ var doc = doc || {}; var filteredItems = $navigation .find('li > a') .filter(function () { - return ( - $(this) - .text() - .toUpperCase() - .indexOf(filterText.toUpperCase()) > -1 - ); + var keywords = ($(this).data('keywords') || "").split(","); + var text = $(this).text(); + + if(text.toUpperCase().indexOf(filterText.toUpperCase()) > -1) + { + return true; + } + + return keywords.some(function(keyword){ + return keyword.toUpperCase().indexOf(filterText.toUpperCase()) > -1; + }); }); filteredItems.each(function () { diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo.Docs.MongoDB.Tests.csproj b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo.Docs.MongoDB.Tests.csproj index 5c3f98c351..90df72d01d 100644 --- a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo.Docs.MongoDB.Tests.csproj +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo.Docs.MongoDB.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs index ad1fce80e5..e1a4d00ce3 100644 --- a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; public class MongoDbFixture : IDisposable { diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs index cb7f48aeb4..e89a68a710 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureDefinitionRecordRepository.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; @@ -20,7 +21,7 @@ public class MongoFeatureDefinitionRecordRepository : public virtual async Task FindByNameAsync(string name, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync( s => s.Name == name, diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs index 3b52fe2634..0eb4f07e2f 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB/Volo/Abp/FeatureManagement/MongoDB/MongoFeatureValueRepository.cs @@ -27,7 +27,7 @@ public class MongoFeatureValueRepository : string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, GetCancellationToken(cancellationToken)); } @@ -38,7 +38,7 @@ public class MongoFeatureValueRepository : string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey).ToListAsync(GetCancellationToken(cancellationToken)); } @@ -47,7 +47,7 @@ public class MongoFeatureValueRepository : string providerKey, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey) .ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo.Abp.FeatureManagement.MongoDB.Tests.csproj b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo.Abp.FeatureManagement.MongoDB.Tests.csproj index bfc01a429f..2f7f5ee7d0 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo.Abp.FeatureManagement.MongoDB.Tests.csproj +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo.Abp.FeatureManagement.MongoDB.Tests.csproj @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs index 8088afee7b..23a5403281 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.FeatureManagement.MongoDB; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs index f17f74b602..8da3237f83 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs @@ -69,6 +69,7 @@ public interface IIdentityUserRepository : IBasicRepository bool includeDetails = false, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -114,6 +115,7 @@ public interface IIdentityUserRepository : IBasicRepository string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs index 86682b85db..a623894625 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -62,35 +62,35 @@ public class EfCoreIdentityUserRepository : EfCoreRepository() - join role in dbContext.Roles on userRole.RoleId equals role.Id - where userIds.Contains(userRole.UserId) - group new - { - userRole.UserId, - role.Name - } by userRole.UserId + join role in dbContext.Roles on userRole.RoleId equals role.Id + where userIds.Contains(userRole.UserId) + group new { + userRole.UserId, + role.Name + } by userRole.UserId into gp - select new IdentityUserIdWithRoleNames - { - Id = gp.Key, RoleNames = gp.Select(x => x.Name).ToArray() - }).ToListAsync(cancellationToken: cancellationToken); + select new IdentityUserIdWithRoleNames + { + Id = gp.Key, + RoleNames = gp.Select(x => x.Name).ToArray() + }).ToListAsync(cancellationToken: cancellationToken); var orgUnitRoles = await (from userOu in dbContext.Set() - join roleOu in dbContext.Set() on userOu.OrganizationUnitId equals roleOu.OrganizationUnitId - join role in dbContext.Roles on roleOu.RoleId equals role.Id - where userIds.Contains(userOu.UserId) - group new - { - userOu.UserId, - role.Name - } by userOu.UserId + join roleOu in dbContext.Set() on userOu.OrganizationUnitId equals roleOu.OrganizationUnitId + join role in dbContext.Roles on roleOu.RoleId equals role.Id + where userIds.Contains(userOu.UserId) + group new { + userOu.UserId, + role.Name + } by userOu.UserId into gp - select new IdentityUserIdWithRoleNames - { - Id = gp.Key, RoleNames = gp.Select(x => x.Name).ToArray() - }).ToListAsync(cancellationToken: cancellationToken); + select new IdentityUserIdWithRoleNames + { + Id = gp.Key, + RoleNames = gp.Select(x => x.Name).ToArray() + }).ToListAsync(cancellationToken: cancellationToken); - return userRoles.Concat(orgUnitRoles).GroupBy(x => x.Id).Select(x => new IdentityUserIdWithRoleNames {Id = x.Key, RoleNames = x.SelectMany(y => y.RoleNames).Distinct().ToArray()}).ToList(); + return userRoles.Concat(orgUnitRoles).GroupBy(x => x.Id).Select(x => new IdentityUserIdWithRoleNames { Id = x.Key, RoleNames = x.SelectMany(y => y.RoleNames).Distinct().ToArray() }).ToList(); } public virtual async Task> GetRoleNamesInOrganizationUnitAsync( @@ -196,6 +196,7 @@ public class EfCoreIdentityUserRepository : EfCoreRepository x.Id == id); + } if (roleId.HasValue) { diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs index ac0b86dee9..6a24fd8ed9 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityClaimTypeRepository.cs @@ -24,13 +24,13 @@ public class MongoIdentityClaimTypeRepository : MongoDbRepository ct.Name == name) .AnyAsync(GetCancellationToken(cancellationToken)); } else { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ct => ct.Id != ignoredId && ct.Name == name) .AnyAsync(GetCancellationToken(cancellationToken)); } @@ -43,15 +43,14 @@ public class MongoIdentityClaimTypeRepository : MongoDbRepository>( + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter) ) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityClaimType.CreationTime) + " desc" : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -59,19 +58,18 @@ public class MongoIdentityClaimTypeRepository : MongoDbRepository>( + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter) ) - .As>() .LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetListByNamesAsync(IEnumerable names, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => names.Contains(x.Name)) .ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityLinkUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityLinkUserRepository.cs index 20116fdd0b..46cfc60eb3 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityLinkUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityLinkUserRepository.cs @@ -18,7 +18,7 @@ public class MongoIdentityLinkUserRepository : MongoDbRepository FindAsync(IdentityLinkUserInfo sourceLinkUserInfo, IdentityLinkUserInfo targetLinkUserInfo, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.SourceUserId == sourceLinkUserInfo.UserId && x.SourceTenantId == sourceLinkUserInfo.TenantId && x.TargetUserId == targetLinkUserInfo.UserId && x.TargetTenantId == targetLinkUserInfo.TenantId || @@ -30,7 +30,7 @@ public class MongoIdentityLinkUserRepository : MongoDbRepository> GetListAsync(IdentityLinkUserInfo linkUserInfo, List excludes = null, CancellationToken cancellationToken = default) { - var query = (await GetMongoQueryableAsync(cancellationToken)).Where(x => + var query = (await GetQueryableAsync(cancellationToken)).Where(x => x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId || x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId); @@ -49,7 +49,7 @@ public class MongoIdentityLinkUserRepository : MongoDbRepository + var linkUsers = await (await GetQueryableAsync(cancellationToken)).Where(x => x.SourceUserId == linkUserInfo.UserId && x.SourceTenantId == linkUserInfo.TenantId || x.TargetUserId == linkUserInfo.UserId && x.TargetTenantId == linkUserInfo.TenantId) .ToListAsync(cancellationToken: GetCancellationToken(cancellationToken)); diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs index 3a57f69902..f977572a04 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs @@ -23,7 +23,7 @@ public class MongoIdentityRoleRepository : MongoDbRepository x.Id) .FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, GetCancellationToken(cancellationToken)); } @@ -38,7 +38,7 @@ public class MongoIdentityRoleRepository : MongoDbRepository x.Id).ToList(); - var userCount = await (await GetMongoQueryableAsync(cancellationToken)) + var userCount = await (await GetQueryableAsync(cancellationToken)) .Where(user => user.Roles.Any(role => roleIds.Contains(role.RoleId))) .SelectMany(user => user.Roles) .GroupBy(userRole => userRole.RoleId) @@ -73,7 +73,7 @@ public class MongoIdentityRoleRepository : MongoDbRepository ids, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(t => ids.Contains(t.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -82,7 +82,7 @@ public class MongoIdentityRoleRepository : MongoDbRepository r.IsDefault) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -91,17 +91,16 @@ public class MongoIdentityRoleRepository : MongoDbRepository x.Name.Contains(filter) || x.NormalizedName.Contains(filter)) - .As>() .LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task RemoveClaimFromAllRolesAsync(string claimType, bool autoSave = false, CancellationToken cancellationToken = default) { - var roles = await (await GetMongoQueryableAsync(cancellationToken)) + var roles = await (await GetQueryableAsync(cancellationToken)) .Where(r => r.Claims.Any(c => c.ClaimType == claimType)) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -121,13 +120,12 @@ public class MongoIdentityRoleRepository : MongoDbRepository x.Name.Contains(filter) || x.NormalizedName.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityRole.CreationTime) + " desc" : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs index f173272009..388c059f1f 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySecurityLogRepository.cs @@ -51,8 +51,7 @@ public class MongoIdentitySecurityLogRepository : ); return await query.OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(IdentitySecurityLog.CreationTime)} desc" : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -83,15 +82,14 @@ public class MongoIdentitySecurityLogRepository : cancellationToken ); - return await query.As>() - .LongCountAsync(GetCancellationToken(cancellationToken)); + return await query.LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetByUserIdAsync(Guid id, Guid userId, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, + return await (await GetQueryableAsync(cancellationToken)).OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, GetCancellationToken(cancellationToken)); } @@ -108,7 +106,7 @@ public class MongoIdentitySecurityLogRepository : string clientIpAddress = null, CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync(cancellationToken)) + return (await GetQueryableAsync(cancellationToken)) .WhereIf(startTime.HasValue, securityLog => securityLog.CreationTime >= startTime.Value) .WhereIf(endTime.HasValue, securityLog => securityLog.CreationTime < endTime.Value.AddDays(1).Date) .WhereIf(!applicationName.IsNullOrWhiteSpace(), securityLog => securityLog.ApplicationName == applicationName) diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs index a1a8f94c49..eb3f99e688 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentitySessionRepository.cs @@ -23,8 +23,7 @@ public class MongoIdentitySessionRepository : MongoDbRepository FindAsync(string sessionId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) - .As>() + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .FirstOrDefaultAsync(x => x.SessionId == sessionId, GetCancellationToken(cancellationToken)); } @@ -41,15 +40,13 @@ public class MongoIdentitySessionRepository : MongoDbRepository ExistAsync(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) - .As>() + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .AnyAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); } public virtual async Task ExistAsync(string sessionId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) - .As>() + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .AnyAsync(x => x.SessionId == sessionId, GetCancellationToken(cancellationToken)); } @@ -62,13 +59,12 @@ public class MongoIdentitySessionRepository : MongoDbRepository x.UserId == userId) .WhereIf(!device.IsNullOrWhiteSpace(), x => x.Device == device) .WhereIf(!clientId.IsNullOrWhiteSpace(), x => x.ClientId == clientId) .OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(IdentitySession.LastAccessed)} desc" : sorting) .PageBy(skipCount, maxResultCount) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -78,11 +74,10 @@ public class MongoIdentitySessionRepository : MongoDbRepository x.UserId == userId) .WhereIf(!device.IsNullOrWhiteSpace(), x => x.Device == device) .WhereIf(!clientId.IsNullOrWhiteSpace(), x => x.ClientId == clientId) - .As>() .LongCountAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs index 56cc48121e..1855069b4a 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserDelegationRepository.cs @@ -24,25 +24,23 @@ public class MongoIdentityUserDelegationRepository : MongoDbRepository> GetListAsync(Guid? sourceUserId, Guid? targetUserId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(sourceUserId.HasValue, x => x.SourceUserId == sourceUserId) .WhereIf(targetUserId.HasValue, x => x.TargetUserId == targetUserId) - .As>() .ToListAsync(cancellationToken: cancellationToken); } public virtual async Task> GetActiveDelegationsAsync(Guid targetUserId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.TargetUserId == targetUserId) .Where(x => x.StartTime <= Clock.Now && x.EndTime >= Clock.Now) - .As>() .ToListAsync(cancellationToken: cancellationToken); } public virtual async Task FindActiveDelegationByIdAsync(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .FirstOrDefaultAsync(x => x.Id == id && x.StartTime <= Clock.Now && diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs index 58486893ec..64c47f8d2c 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -25,7 +25,7 @@ public class MongoIdentityUserRepository : MongoDbRepository x.Id) .FirstOrDefaultAsync( u => u.NormalizedUserName == normalizedUserName, @@ -43,13 +43,13 @@ public class MongoIdentityUserRepository : MongoDbRepository r.OrganizationUnitId) .ToArray(); - var organizationUnits = await (await GetMongoQueryableAsync(cancellationToken)) + var organizationUnits = await (await GetQueryableAsync(cancellationToken)) .Where(ou => organizationUnitIds.Contains(ou.Id)) .ToListAsync(cancellationToken: cancellationToken); var orgUnitRoleIds = organizationUnits.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToArray(); var roleIds = user.Roles.Select(r => r.RoleId).ToArray(); var allRoleIds = orgUnitRoleIds.Union(roleIds); - return await (await GetMongoQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).Select(r => r.Name).ToListAsync(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).Select(r => r.Name).ToListAsync(cancellationToken); } public virtual async Task> GetRoleNamesInOrganizationUnitAsync( @@ -63,13 +63,13 @@ public class MongoIdentityUserRepository : MongoDbRepository r.OrganizationUnitId) .ToArray(); - var organizationUnits = await (await GetMongoQueryableAsync(cancellationToken)) + var organizationUnits = await (await GetQueryableAsync(cancellationToken)) .Where(ou => organizationUnitIds.Contains(ou.Id)) .ToListAsync(cancellationToken: cancellationToken); var roleIds = organizationUnits.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToArray(); - var queryable = await GetMongoQueryableAsync(cancellationToken); + var queryable = await GetQueryableAsync(cancellationToken); return await queryable .Where(r => roleIds.Contains(r.Id)) @@ -83,7 +83,7 @@ public class MongoIdentityUserRepository : MongoDbRepository u.Logins.Any(login => login.LoginProvider == loginProvider && login.ProviderKey == providerKey)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); @@ -94,7 +94,7 @@ public class MongoIdentityUserRepository : MongoDbRepository x.Id).FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, GetCancellationToken(cancellationToken)); } @@ -103,14 +103,14 @@ public class MongoIdentityUserRepository : MongoDbRepository u.Claims.Any(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value)) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task RemoveClaimFromAllUsersAsync(string claimType, bool autoSave, CancellationToken cancellationToken = default) { - var users = await (await GetMongoQueryableAsync(cancellationToken)) + var users = await (await GetQueryableAsync(cancellationToken)) .Where(u => u.Claims.Any(c => c.ClaimType == claimType)) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -129,7 +129,7 @@ public class MongoIdentityUserRepository : MongoDbRepository(cancellationToken); + var queryable = await GetQueryableAsync(cancellationToken); var role = await queryable .Where(x => x.NormalizedName == normalizedRoleName) @@ -141,7 +141,7 @@ public class MongoIdentityUserRepository : MongoDbRepository(); } - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(u => u.Roles.Any(r => r.RoleId == role.Id)) .ToListAsync(cancellationToken); } @@ -150,7 +150,7 @@ public class MongoIdentityUserRepository : MongoDbRepository u.Roles.Any(r => r.RoleId == roleId)) .Select(x => x.Id) .ToListAsync(cancellationToken); @@ -164,6 +164,7 @@ public class MongoIdentityUserRepository : MongoDbRepository>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -217,13 +218,13 @@ public class MongoIdentityUserRepository : MongoDbRepository r.OrganizationUnitId) .ToArray(); - var organizationUnits = await (await GetMongoQueryableAsync(cancellationToken)) + var organizationUnits = await (await GetQueryableAsync(cancellationToken)) .Where(ou => organizationUnitIds.Contains(ou.Id)) .ToListAsync(cancellationToken: cancellationToken); var orgUnitRoleIds = organizationUnits.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToArray(); var roleIds = user.Roles.Select(r => r.RoleId).ToArray(); var allRoleIds = orgUnitRoleIds.Union(roleIds); - return await (await GetMongoQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).ToListAsync(cancellationToken); + return await (await GetQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).ToListAsync(cancellationToken); } public virtual async Task> GetOrganizationUnitsAsync( @@ -235,7 +236,7 @@ public class MongoIdentityUserRepository : MongoDbRepository r.OrganizationUnitId); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ou => organizationUnitIds.Contains(ou.Id)) .ToListAsync(cancellationToken); } @@ -244,6 +245,7 @@ public class MongoIdentityUserRepository : MongoDbRepository u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnitId)) .ToListAsync(GetCancellationToken(cancellationToken)); return result; @@ -296,7 +299,7 @@ public class MongoIdentityUserRepository : MongoDbRepository organizationUnitIds, CancellationToken cancellationToken = default) { - var result = await (await GetMongoQueryableAsync(cancellationToken)) + var result = await (await GetQueryableAsync(cancellationToken)) .Where(u => u.OrganizationUnits.Any(uou => organizationUnitIds.Contains(uou.OrganizationUnitId))) .ToListAsync(GetCancellationToken(cancellationToken)); return result; @@ -308,12 +311,12 @@ public class MongoIdentityUserRepository : MongoDbRepository(cancellationToken)) + var organizationUnitIds = await (await GetQueryableAsync(cancellationToken)) .Where(ou => ou.Code.StartsWith(code)) .Select(ou => ou.Id) .ToListAsync(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(u => u.OrganizationUnits.Any(uou => organizationUnitIds.Contains(uou.OrganizationUnitId))) .ToListAsync(cancellationToken); } @@ -324,7 +327,7 @@ public class MongoIdentityUserRepository : MongoDbRepository u.TenantId == tenantId && u.UserName == userName, GetCancellationToken(cancellationToken) @@ -333,14 +336,14 @@ public class MongoIdentityUserRepository : MongoDbRepository> GetListByIdsAsync(IEnumerable ids, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => ids.Contains(x.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task UpdateRoleAsync(Guid sourceRoleId, Guid? targetRoleId, CancellationToken cancellationToken = default) { - var users = await (await GetMongoQueryableAsync(cancellationToken)) + var users = await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Roles.Any(r => r.RoleId == sourceRoleId)) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -358,7 +361,7 @@ public class MongoIdentityUserRepository : MongoDbRepository x.OrganizationUnits.Any(r => r.OrganizationUnitId == sourceOrganizationId)) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -390,7 +393,7 @@ public class MongoIdentityUserRepository : MongoDbRepository x.Value); var roleIds = userAndRoleIds.SelectMany(x => x.Value); - var organizationUnitAndRoleIds = await (await GetMongoQueryableAsync(cancellationToken)).Where(ou => organizationUnitIds.Contains(ou.Id)) + var organizationUnitAndRoleIds = await (await GetQueryableAsync(cancellationToken)).Where(ou => organizationUnitIds.Contains(ou.Id)) .Select(userOrganizationUnit => new { userOrganizationUnit.Id, @@ -399,7 +402,8 @@ public class MongoIdentityUserRepository : MongoDbRepository x.Roles.Select(r => r.RoleId)).ToList(); var allRoleIds = roleIds.Union(allOrganizationUnitRoleIds); - var roles = await (await GetMongoQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).Select(r => new{ r.Id, r.Name }).ToListAsync(cancellationToken); + + var roles = await (await GetQueryableAsync(cancellationToken)).Where(r => allRoleIds.Contains(r.Id)).Select(r => new{ r.Id, r.Name }).ToListAsync(cancellationToken); var userRoles = userAndRoleIds.ToDictionary(x => x.Key, x => roles.Where(r => x.Value.Contains(r.Id)).Select(r => r.Name).ToArray()); var result = userRoles.Select(x => new IdentityUserIdWithRoleNames { Id = x.Key, RoleNames = x.Value }).ToList(); @@ -413,19 +417,20 @@ public class MongoIdentityUserRepository : MongoDbRepository> GetFilteredQueryableAsync( + protected virtual async Task> GetFilteredQueryableAsync( string filter = null, Guid? roleId = null, Guid? organizationUnitId = null, + Guid? id = null, string userName = null, string phoneNumber = null, string emailAddress = null, @@ -442,11 +447,16 @@ public class MongoIdentityUserRepository : MongoDbRepository x.Id == id); + } + if (roleId.HasValue) { - var organizationUnitIds = (await GetMongoQueryableAsync(cancellationToken)) + var organizationUnitIds = (await GetQueryableAsync(cancellationToken)) .Where(ou => ou.Roles.Any(r => r.RoleId == roleId.Value)) .Select(userOrganizationUnit => userOrganizationUnit.Id) .ToArray(); @@ -455,7 +465,7 @@ public class MongoIdentityUserRepository : MongoDbRepository>( + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.NormalizedUserName.Contains(upperFilter) || @@ -464,20 +474,20 @@ public class MongoIdentityUserRepository : MongoDbRepository>(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value)) - .WhereIf>(!string.IsNullOrWhiteSpace(userName), x => x.UserName == userName) - .WhereIf>(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber == phoneNumber) - .WhereIf>(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) - .WhereIf>(!string.IsNullOrWhiteSpace(name), x => x.Name == name) - .WhereIf>(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) - .WhereIf>(isLockedOut.HasValue && isLockedOut.Value, x => x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow) - .WhereIf>(isLockedOut.HasValue && !isLockedOut.Value, x => !(x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow)) - .WhereIf>(notActive.HasValue, x => x.IsActive == !notActive.Value) - .WhereIf>(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value) - .WhereIf>(isExternal.HasValue, x => x.IsExternal == isExternal.Value) - .WhereIf>(maxCreationTime != null, p => p.CreationTime <= maxCreationTime) - .WhereIf>(minCreationTime != null, p => p.CreationTime >= minCreationTime) - .WhereIf>(maxModifitionTime != null, p => p.LastModificationTime <= maxModifitionTime) - .WhereIf>(minModifitionTime != null, p => p.LastModificationTime >= minModifitionTime); + .WhereIf(organizationUnitId.HasValue, identityUser => identityUser.OrganizationUnits.Any(x => x.OrganizationUnitId == organizationUnitId.Value)) + .WhereIf(!string.IsNullOrWhiteSpace(userName), x => x.UserName == userName) + .WhereIf(!string.IsNullOrWhiteSpace(phoneNumber), x => x.PhoneNumber == phoneNumber) + .WhereIf(!string.IsNullOrWhiteSpace(emailAddress), x => x.Email == emailAddress) + .WhereIf(!string.IsNullOrWhiteSpace(name), x => x.Name == name) + .WhereIf(!string.IsNullOrWhiteSpace(surname), x => x.Surname == surname) + .WhereIf(isLockedOut.HasValue && isLockedOut.Value, x => x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow) + .WhereIf(isLockedOut.HasValue && !isLockedOut.Value, x => !(x.LockoutEnabled && x.LockoutEnd != null && x.LockoutEnd > DateTimeOffset.UtcNow)) + .WhereIf(notActive.HasValue, x => x.IsActive == !notActive.Value) + .WhereIf(emailConfirmed.HasValue, x => x.EmailConfirmed == emailConfirmed.Value) + .WhereIf(isExternal.HasValue, x => x.IsExternal == isExternal.Value) + .WhereIf(maxCreationTime != null, p => p.CreationTime <= maxCreationTime) + .WhereIf(minCreationTime != null, p => p.CreationTime >= minCreationTime) + .WhereIf(maxModifitionTime != null, p => p.LastModificationTime <= maxModifitionTime) + .WhereIf(minModifitionTime != null, p => p.LastModificationTime >= minModifitionTime); } } diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs index 78838c9c6c..14514e0ab4 100644 --- a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs @@ -27,7 +27,7 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ou => ou.ParentId == parentId) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -38,7 +38,7 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -48,7 +48,7 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(t => ids.Contains(t.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -58,7 +58,7 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Roles.Any(r => r.RoleId == roleId)) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -68,7 +68,7 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => displayNames.Contains(x.DisplayName)) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -80,10 +80,9 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(sorting.IsNullOrEmpty() ? nameof(OrganizationUnit.CreationTime) + " desc" : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -92,7 +91,7 @@ public class MongoOrganizationUnitRepository bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync( ou => ou.DisplayName == displayName, @@ -110,11 +109,10 @@ public class MongoOrganizationUnitRepository { var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray(); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(r => roleIds.Contains(r.Id)) .OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -126,17 +124,16 @@ public class MongoOrganizationUnitRepository bool includeDetails = false, CancellationToken cancellationToken = default) { - var organizationUnits = await (await GetMongoQueryableAsync(cancellationToken)) + var organizationUnits = await (await GetQueryableAsync(cancellationToken)) .Where(ou => organizationUnitIds.Contains(ou.Id)) .ToListAsync(GetCancellationToken(cancellationToken)); var roleIds = organizationUnits.SelectMany(ou => ou.Roles.Select(r => r.RoleId)).ToArray(); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(r => roleIds.Contains(r.Id)) .OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -146,7 +143,7 @@ public class MongoOrganizationUnitRepository { var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray(); - return await (await GetMongoQueryableAsync(cancellationToken)).Where(r => roleIds.Contains(r.Id)).CountAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(r => roleIds.Contains(r.Id)).CountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> GetUnaddedRolesAsync( @@ -160,12 +157,11 @@ public class MongoOrganizationUnitRepository { var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray(); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(r => !roleIds.Contains(r.Id)) .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) .OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -176,10 +172,9 @@ public class MongoOrganizationUnitRepository { var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray(); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(r => !roleIds.Contains(r.Id)) .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter)) - .As>() .CountAsync(GetCancellationToken(cancellationToken)); } @@ -196,15 +191,14 @@ public class MongoOrganizationUnitRepository var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, cancellationToken); return await query .OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(cancellationToken); } public virtual async Task> GetMemberIdsAsync(Guid id, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == id)).Select(x => x.Id) .ToListAsync(cancellationToken); } @@ -229,9 +223,9 @@ public class MongoOrganizationUnitRepository CancellationToken cancellationToken = default) { return await - (await GetMongoQueryableAsync(cancellationToken)) + (await GetQueryableAsync(cancellationToken)) .Where(u => !u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id)) - .WhereIf>( + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.UserName.Contains(filter) || @@ -239,24 +233,22 @@ public class MongoOrganizationUnitRepository (u.PhoneNumber != null && u.PhoneNumber.Contains(filter)) ) .OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetUnaddedUsersCountAsync(OrganizationUnit organizationUnit, string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(u => !u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id)) - .WhereIf>( + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.UserName.Contains(filter) || u.Email.Contains(filter) || (u.PhoneNumber != null && u.PhoneNumber.Contains(filter)) ) - .As>() .CountAsync(GetCancellationToken(cancellationToken)); } @@ -269,11 +261,10 @@ public class MongoOrganizationUnitRepository public virtual async Task RemoveAllMembersAsync(OrganizationUnit organizationUnit, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - var userQueryable = await GetMongoQueryableAsync(cancellationToken); + var userQueryable = await GetQueryableAsync(cancellationToken); var dbContext = await GetDbContextAsync(cancellationToken); var users = await userQueryable .Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id)) - .As>() .ToListAsync(cancellationToken); foreach (var user in users) @@ -283,14 +274,14 @@ public class MongoOrganizationUnitRepository } } - protected virtual async Task> CreateGetMembersFilteredQueryAsync( + protected virtual async Task> CreateGetMembersFilteredQueryAsync( OrganizationUnit organizationUnit, string filter = null, CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync(cancellationToken)) + return (await GetQueryableAsync(cancellationToken)) .Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id)) - .WhereIf>( + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.UserName.Contains(filter) || diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj index cc463e1cc8..311ee9fa69 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj @@ -20,10 +20,10 @@ - - - - + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs index 732500214c..90b0f60b14 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.Identity.MongoDB; diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs index a77cb01d8a..5259c31329 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs @@ -1,7 +1,9 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.BackgroundWorkers; +using Volo.Abp.DistributedLocking; using Volo.Abp.Threading; namespace Volo.Abp.IdentityServer.Tokens; @@ -9,24 +11,40 @@ namespace Volo.Abp.IdentityServer.Tokens; public class TokenCleanupBackgroundWorker : AsyncPeriodicBackgroundWorkerBase { protected TokenCleanupOptions Options { get; } + protected IAbpDistributedLock DistributedLock { get; } public TokenCleanupBackgroundWorker( AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory, - IOptions options) + IOptions options, + IAbpDistributedLock distributedLock) : base( timer, serviceScopeFactory) { + DistributedLock = distributedLock; Options = options.Value; timer.Period = Options.CleanupPeriod; } protected async override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) { - await workerContext - .ServiceProvider - .GetRequiredService() - .CleanAsync(); + await using (var handle = await DistributedLock.TryAcquireAsync(nameof(TokenCleanupBackgroundWorker))) + { + Logger.LogInformation($"Lock is acquired for {nameof(TokenCleanupBackgroundWorker)}"); + + if (handle != null) + { + await workerContext + .ServiceProvider + .GetRequiredService() + .CleanAsync(); + + Logger.LogInformation($"Lock is released for {nameof(TokenCleanupBackgroundWorker)}"); + return; + } + + Logger.LogInformation($"Handle is null because of the locking for : {nameof(TokenCleanupBackgroundWorker)}"); + } } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiResourceRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiResourceRepository.cs index 24dc519a40..42379b44f9 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiResourceRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiResourceRepository.cs @@ -20,7 +20,7 @@ public class MongoApiResourceRepository : MongoDbRepository FindByNameAsync(string apiResourceName, bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(ar => ar.Id) .FirstOrDefaultAsync(ar => ar.Name == apiResourceName, GetCancellationToken(cancellationToken)); } @@ -28,7 +28,7 @@ public class MongoApiResourceRepository : MongoDbRepository> FindByNameAsync(string[] apiResourceNames, bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ar => apiResourceNames.Contains(ar.Name)) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -36,7 +36,7 @@ public class MongoApiResourceRepository : MongoDbRepository> GetListByScopesAsync(string[] scopeNames, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ar => ar.Scopes.Any(x => scopeNames.Contains(x.Scope))) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -44,21 +44,20 @@ public class MongoApiResourceRepository : MongoDbRepository> GetListAsync(string sorting, int skipCount, int maxResultCount, string filter, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.Description.Contains(filter) || x.DisplayName.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(ApiResource.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>(!filter.IsNullOrWhiteSpace(), + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.Description.Contains(filter) || x.DisplayName.Contains(filter)) @@ -67,7 +66,7 @@ public class MongoApiResourceRepository : MongoDbRepository CheckNameExistAsync(string name, Guid? expectedId = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .AnyAsync(ar => ar.Id != expectedId && ar.Name == name, GetCancellationToken(cancellationToken)); } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiScopeRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiScopeRepository.cs index 01c1ecad07..760bd0522c 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiScopeRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoApiScopeRepository.cs @@ -22,7 +22,7 @@ public class MongoApiScopeRepository : MongoDbRepository FindByNameAsync(string scopeName, bool includeDetails = true, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(x => x.Name == scopeName, GetCancellationToken(cancellationToken)); } @@ -30,7 +30,7 @@ public class MongoApiScopeRepository : MongoDbRepository> GetListByNameAsync(string[] scopeNames, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(scope => scopeNames.Contains(scope.Name)) .OrderBy(scope => scope.Id) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -39,21 +39,20 @@ public class MongoApiScopeRepository : MongoDbRepository> GetListAsync(string sorting, int skipCount, int maxResultCount, string filter = null, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.Description.Contains(filter) || x.DisplayName.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(ApiScope.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>(!filter.IsNullOrWhiteSpace(), + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.Description.Contains(filter) || x.DisplayName.Contains(filter)) @@ -62,7 +61,7 @@ public class MongoApiScopeRepository : MongoDbRepository CheckNameExistAsync(string name, Guid? expectedId = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .AnyAsync(x => x.Id != expectedId && x.Name == name, GetCancellationToken(cancellationToken)); } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoClientRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoClientRepository.cs index c7f81f51e2..d266ac7c52 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoClientRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoClientRepository.cs @@ -26,7 +26,7 @@ public class MongoClientRepository : MongoDbRepository x.Id) .FirstOrDefaultAsync(x => x.ClientId == clientId, GetCancellationToken(cancellationToken)); } @@ -39,18 +39,17 @@ public class MongoClientRepository : MongoDbRepository x.ClientId.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(Client.ClientName) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>(!filter.IsNullOrWhiteSpace(), + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.ClientId.Contains(filter)) .LongCountAsync(GetCancellationToken(cancellationToken)); } @@ -58,7 +57,7 @@ public class MongoClientRepository : MongoDbRepository> GetAllDistinctAllowedCorsOriginsAsync( CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .SelectMany(x => x.AllowedCorsOrigins) .Select(y => y.Origin) .Distinct() @@ -67,7 +66,7 @@ public class MongoClientRepository : MongoDbRepository CheckClientIdExistAsync(string clientId, Guid? expectedId = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .AnyAsync(c => c.Id != expectedId && c.ClientId == clientId, GetCancellationToken(cancellationToken)); } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs index 921225a5ae..65d74b975a 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; @@ -23,7 +24,7 @@ public class MongoDeviceFlowCodesRepository : string userCode, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(d => d.UserCode == userCode) .OrderBy(x => x.Id) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); @@ -31,7 +32,7 @@ public class MongoDeviceFlowCodesRepository : public virtual async Task FindByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(d => d.DeviceCode == deviceCode) .OrderBy(x => x.Id) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); @@ -42,7 +43,7 @@ public class MongoDeviceFlowCodesRepository : int maxResultCount, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Expiration != null && x.Expiration < maxExpirationDate) .OrderBy(x => x.ClientId) .Take(maxResultCount) diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoIdentityResourceRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoIdentityResourceRepository.cs index faf3d66d96..9a3ee0f815 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoIdentityResourceRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoIdentityResourceRepository.cs @@ -20,20 +20,19 @@ public class MongoIdentityResourceRepository : MongoDbRepository> GetListAsync(string sorting, int skipCount, int maxResultCount, string filter, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.Description.Contains(filter) || x.DisplayName.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(IdentityResource.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>(!filter.IsNullOrWhiteSpace(), + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.Description.Contains(filter) || x.DisplayName.Contains(filter)) @@ -45,7 +44,7 @@ public class MongoIdentityResourceRepository : MongoDbRepository x.Name == name) .OrderBy(x => x.Id) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); @@ -54,14 +53,14 @@ public class MongoIdentityResourceRepository : MongoDbRepository> GetListByScopeNameAsync(string[] scopeNames, bool includeDetails = false, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(ar => scopeNames.Contains(ar.Name)) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task CheckNameExistAsync(string name, Guid? expectedId = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .AnyAsync(ir => ir.Id != expectedId && ir.Name == name, GetCancellationToken(cancellationToken)); } } diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistentGrantRepository.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistentGrantRepository.cs index e3f3cc8368..4ac0844bbe 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistentGrantRepository.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistentGrantRepository.cs @@ -27,7 +27,7 @@ public class MongoPersistentGrantRepository : MongoDbRepository FindByKeyAsync(string key, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Key == key) .OrderBy(x => x.Id) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); @@ -35,7 +35,7 @@ public class MongoPersistentGrantRepository : MongoDbRepository> GetListBySubjectIdAsync(string subjectId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.SubjectId == subjectId) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -43,7 +43,7 @@ public class MongoPersistentGrantRepository : MongoDbRepository> GetListByExpirationAsync(DateTime maxExpirationDate, int maxResultCount, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Expiration != null && x.Expiration < maxExpirationDate) .OrderBy(x => x.ClientId) .Take(maxResultCount) @@ -87,18 +87,17 @@ public class MongoPersistentGrantRepository : MongoDbRepository> FilterAsync( + private async Task> FilterAsync( string subjectId, string sessionId, string clientId, string type, CancellationToken cancellationToken = default) { - return (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>(!subjectId.IsNullOrWhiteSpace(), x => x.SubjectId == subjectId) - .WhereIf>(!sessionId.IsNullOrWhiteSpace(), x => x.SessionId == sessionId) - .WhereIf>(!clientId.IsNullOrWhiteSpace(), x => x.ClientId == clientId) - .WhereIf>(!type.IsNullOrWhiteSpace(), x => x.Type == type) - .As>(); + return (await GetQueryableAsync(cancellationToken)) + .WhereIf(!subjectId.IsNullOrWhiteSpace(), x => x.SubjectId == subjectId) + .WhereIf(!sessionId.IsNullOrWhiteSpace(), x => x.SessionId == sessionId) + .WhereIf(!clientId.IsNullOrWhiteSpace(), x => x.ClientId == clientId) + .WhereIf(!type.IsNullOrWhiteSpace(), x => x.Type == type); } } diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo.Abp.IdentityServer.MongoDB.Tests.csproj b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo.Abp.IdentityServer.MongoDB.Tests.csproj index d2d7bf8a0e..07377805af 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo.Abp.IdentityServer.MongoDB.Tests.csproj +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo.Abp.IdentityServer.MongoDB.Tests.csproj @@ -20,10 +20,10 @@ - - - - + + + + diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs index 2f6d52d255..6611beb635 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.IdentityServer; diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Applications/MongoOpenIddictApplicationRepository.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Applications/MongoOpenIddictApplicationRepository.cs index ad9064e8cf..867bbe71c5 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Applications/MongoOpenIddictApplicationRepository.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Applications/MongoOpenIddictApplicationRepository.cs @@ -21,50 +21,47 @@ public class MongoOpenIddictApplicationRepository : MongoDbRepository> GetListAsync(string sorting, int skipCount, int maxResultCount, string filter = null, CancellationToken cancellationToken = default) { - return await ((await GetMongoQueryableAsync(cancellationToken))) + return await ((await GetQueryableAsync(cancellationToken))) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.ClientId.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(OpenIddictApplication.CreationTime) + " desc" : sorting) .PageBy(skipCount, maxResultCount) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await ((await GetMongoQueryableAsync(cancellationToken))) + return await ((await GetQueryableAsync(cancellationToken))) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.ClientId.Contains(filter)) - .As>() .LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task FindByClientIdAsync(string clientId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .FirstOrDefaultAsync(x => x.ClientId == clientId, cancellationToken); } public virtual async Task> FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.PostLogoutRedirectUris.Contains(address)).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(x => x.PostLogoutRedirectUris.Contains(address)).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> FindByRedirectUriAsync(string address, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).Where(x => x.RedirectUris.Contains(address)).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).Where(x => x.RedirectUris.Contains(address)).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken = default) { - return await query(await GetMongoQueryableAsync(cancellationToken), state).As>().FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + return await query(await GetQueryableAsync(cancellationToken), state).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> ListAsync(int? count, int? offset, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .SkipIf>(offset.HasValue, offset) .TakeIf>(count.HasValue, count) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Authorizations/MongoOpenIddictAuthorizationRepository.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Authorizations/MongoOpenIddictAuthorizationRepository.cs index f4bd5f4817..87ed933f00 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Authorizations/MongoOpenIddictAuthorizationRepository.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Authorizations/MongoOpenIddictAuthorizationRepository.cs @@ -29,53 +29,52 @@ public class MongoOpenIddictAuthorizationRepository : MongoDbRepository> FindAsync(string subject, Guid? client, string status, string type, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!subject.IsNullOrWhiteSpace(), x => x.Subject == subject) .WhereIf(client.HasValue, x => x.ApplicationId == client) .WhereIf(!status.IsNullOrWhiteSpace(), x => x.Status == status) .WhereIf(!type.IsNullOrWhiteSpace(), x => x.Type == type) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> FindByApplicationIdAsync(Guid applicationId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(x => x.ApplicationId == applicationId).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))).Where(x => x.ApplicationId == applicationId).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); } public virtual async Task> FindBySubjectAsync(string subject, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).Where(x => x.Subject == subject).ToListAsync(GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))).Where(x => x.Subject == subject).ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> ListAsync(int? count, int? offset, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .OrderBy(authorization => authorization.Id!) .SkipIf>(offset.HasValue, offset) .TakeIf>(count.HasValue, count) - .As>().ToListAsync(GetCancellationToken(cancellationToken)); + .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task PruneAsync(DateTime date, CancellationToken cancellationToken = default) { - var tokenIds = await (await GetMongoQueryableAsync(cancellationToken)) + var tokenIds = await (await GetQueryableAsync(cancellationToken)) .Where(x => x.AuthorizationId != null) .Select(x => x.AuthorizationId.Value) .ToListAsync(GetCancellationToken(cancellationToken)); - var authorizations = await (await GetMongoQueryableAsync(cancellationToken)) + var authorizations = await (await GetQueryableAsync(cancellationToken)) .Where(x => x.CreationDate < date) .Where(x => x.Status != OpenIddictConstants.Statuses.Valid || (x.Type == OpenIddictConstants.AuthorizationTypes.AdHoc && !tokenIds.Contains(x.Id))) .Select(x => x.Id) .ToListAsync(cancellationToken: cancellationToken); - var tokens = await (await GetMongoQueryableAsync(cancellationToken)) + var tokens = await (await GetQueryableAsync(cancellationToken)) .Where(x => x.AuthorizationId != null && authorizations.Contains(x.AuthorizationId.Value)) .ToListAsync(cancellationToken: cancellationToken); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Scopes/MongoOpenIddictScopeRepository.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Scopes/MongoOpenIddictScopeRepository.cs index 3e16df4e9b..7323b3f272 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Scopes/MongoOpenIddictScopeRepository.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Scopes/MongoOpenIddictScopeRepository.cs @@ -21,61 +21,56 @@ public class MongoOpenIddictScopeRepository : MongoDbRepository> GetListAsync(string sorting, int skipCount, int maxResultCount, string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.DisplayName.Contains(filter) || x.Description.Contains(filter)) .OrderBy(sorting.IsNullOrWhiteSpace() ? nameof(OpenIddictScope.CreationTime) + " desc" : sorting) .PageBy(skipCount, maxResultCount) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!filter.IsNullOrWhiteSpace(), x => x.Name.Contains(filter) || x.DisplayName.Contains(filter) || x.Description.Contains(filter)) - .As>() .LongCountAsync(GetCancellationToken(cancellationToken)); } public virtual async Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); } public virtual async Task FindByNameAsync(string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.Name == name, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(cancellationToken)).FirstOrDefaultAsync(x => x.Name == name, GetCancellationToken(cancellationToken)); } public virtual async Task> FindByNamesAsync(string[] names, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => names.Contains(x.Name)) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> FindByResourceAsync(string resource, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.Resources.Contains(resource)) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> ListAsync(int? count, int? offset, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .OrderBy(x => x.Id) .SkipIf>(offset.HasValue, offset) .TakeIf>(count.HasValue, count) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Tokens/MongoOpenIddictTokenRepository.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Tokens/MongoOpenIddictTokenRepository.cs index 175b137515..2b2a9bec12 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Tokens/MongoOpenIddictTokenRepository.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.MongoDB/Volo/Abp/OpenIddict/Tokens/MongoOpenIddictTokenRepository.cs @@ -21,7 +21,7 @@ public class MongoOpenIddictTokenRepository : MongoDbRepository x.ApplicationId == applicationId) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -31,7 +31,7 @@ public class MongoOpenIddictTokenRepository : MongoDbRepository x.AuthorizationId == authorizationId) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -40,7 +40,7 @@ public class MongoOpenIddictTokenRepository : MongoDbRepository x.AuthorizationId != null && authorizationIds.Contains(x.AuthorizationId.Value)) .ToListAsync(GetCancellationToken(cancellationToken)); @@ -49,67 +49,62 @@ public class MongoOpenIddictTokenRepository : MongoDbRepository> FindAsync(string subject, Guid? client, string status, string type, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .WhereIf(!subject.IsNullOrWhiteSpace(), x => x.Subject == subject) .WhereIf(client.HasValue, x => x.ApplicationId == client) .WhereIf(!status.IsNullOrWhiteSpace(), x => x.Status == status) .WhereIf(!type.IsNullOrWhiteSpace(), x => x.Type == type) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> FindByApplicationIdAsync(Guid applicationId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.ApplicationId == applicationId) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> FindByAuthorizationIdAsync(Guid authorizationId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.AuthorizationId == authorizationId) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken)); } public virtual async Task FindByReferenceIdAsync(string referenceId, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(x => x.ReferenceId == referenceId, GetCancellationToken(cancellationToken)); + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))).FirstOrDefaultAsync(x => x.ReferenceId == referenceId, GetCancellationToken(cancellationToken)); } public virtual async Task> FindBySubjectAsync(string subject, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.Subject == subject) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task> ListAsync(int? count, int? offset, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + return await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .OrderBy(x => x.Id) .SkipIf>(offset.HasValue, offset) .TakeIf>(count.HasValue, count) - .As>() .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task PruneAsync(DateTime date, CancellationToken cancellationToken = default) { - var authorizationIds = await (await GetMongoQueryableAsync(cancellationToken)) + var authorizationIds = await (await GetQueryableAsync(cancellationToken)) .Where(x => x.Status != OpenIddictConstants.Statuses.Valid) .Select(x => x.Id) .ToListAsync(GetCancellationToken(cancellationToken)); - var tokens = await (await GetMongoQueryableAsync(GetCancellationToken(cancellationToken))) + var tokens = await (await GetQueryableAsync(GetCancellationToken(cancellationToken))) .Where(x => x.CreationDate < date) .Where(x => (x.Status != OpenIddictConstants.Statuses.Inactive && x.Status != OpenIddictConstants.Statuses.Valid) || diff --git a/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo.Abp.OpenIddict.MongoDB.Tests.csproj b/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo.Abp.OpenIddict.MongoDB.Tests.csproj index 56512b464c..d5f8903293 100644 --- a/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo.Abp.OpenIddict.MongoDB.Tests.csproj +++ b/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo.Abp.OpenIddict.MongoDB.Tests.csproj @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo/Abp/OpenIddict/MongoDB/MongoDbFixture.cs b/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo/Abp/OpenIddict/MongoDB/MongoDbFixture.cs index ddceaf8104..1716d478a5 100644 --- a/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo/Abp/OpenIddict/MongoDB/MongoDbFixture.cs +++ b/modules/openiddict/test/Volo.Abp.OpenIddict.MongoDB.Tests/Volo/Abp/OpenIddict/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.OpenIddict.MongoDB; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionDefinitionRecordRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionDefinitionRecordRepository.cs index 529f56511c..2f98db4f60 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionDefinitionRecordRepository.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionDefinitionRecordRepository.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; @@ -22,7 +23,7 @@ public class MongoPermissionDefinitionRecordRepository : CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync( s => s.Name == name, diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionGrantRepository.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionGrantRepository.cs index aa7694b7d5..d83d916890 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionGrantRepository.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB/Volo/Abp/PermissionManagement/MongoDb/MongoPermissionGrantRepository.cs @@ -27,7 +27,7 @@ public class MongoPermissionGrantRepository : CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(s => s.Name == name && @@ -43,7 +43,7 @@ public class MongoPermissionGrantRepository : CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(s => s.ProviderName == providerName && s.ProviderKey == providerKey @@ -54,7 +54,7 @@ public class MongoPermissionGrantRepository : CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(s => names.Contains(s.Name) && s.ProviderName == providerName && diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj index 757b0f164c..0f9e0169e8 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo.Abp.PermissionManagement.MongoDB.Tests.csproj @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs index fc039d7ee2..05fae7288c 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.PermissionManagement.MongoDB; diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingDefinitionRecordRepository.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingDefinitionRecordRepository.cs index d2500cccd2..3ea0206759 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingDefinitionRecordRepository.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingDefinitionRecordRepository.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using System.Linq; using MongoDB.Driver.Linq; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; @@ -16,7 +17,7 @@ public class MongoSettingDefinitionRecordRepository : MongoDbRepository FindByNameAsync(string name, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(s => s.Name == name, GetCancellationToken(cancellationToken)); } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs index 74c680e7e2..ea264b254e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.MongoDB/Volo/Abp/SettingManagement/MongoDB/MongoSettingRepository.cs @@ -24,7 +24,7 @@ public class MongoSettingRepository : MongoDbRepository x.Id) .FirstOrDefaultAsync( s => s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, @@ -36,7 +36,7 @@ public class MongoSettingRepository : MongoDbRepository s.ProviderName == providerName && s.ProviderKey == providerKey) .ToListAsync(GetCancellationToken(cancellationToken)); } @@ -47,7 +47,7 @@ public class MongoSettingRepository : MongoDbRepository names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey) .ToListAsync(GetCancellationToken(cancellationToken)); } diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo.Abp.SettingManagement.MongoDB.Tests.csproj b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo.Abp.SettingManagement.MongoDB.Tests.csproj index 65ea9436ee..9c2c2f36a9 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo.Abp.SettingManagement.MongoDB.Tests.csproj +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo.Abp.SettingManagement.MongoDB.Tests.csproj @@ -18,10 +18,10 @@ - - - - + + + + diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs index 625b66b328..bc5feb98b1 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace Volo.Abp.SettingManagement.MongoDB; diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs index 049dc5e57c..758ece4d71 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs @@ -24,21 +24,21 @@ public class MongoTenantRepository : MongoDbRepository t.NormalizedName == normalizedName, GetCancellationToken(cancellationToken)); } [Obsolete("Use FindByNameAsync method.")] public virtual Tenant FindByName(string normalizedName, bool includeDetails = true) { - return GetMongoQueryable() + return GetQueryable() .FirstOrDefault(t => t.NormalizedName == normalizedName); } [Obsolete("Use FindAsync method.")] public virtual Tenant FindById(Guid id, bool includeDetails = true) { - return GetMongoQueryable() + return GetQueryable() .FirstOrDefault(t => t.Id == id); } @@ -50,22 +50,21 @@ public class MongoTenantRepository : MongoDbRepository>( + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter) ) .OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(GetCancellationToken(cancellationToken)); } public virtual async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>( + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.Name.Contains(filter) diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj index c22da31998..2913a0ed53 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo.Abp.TenantManagement.MongoDB.Tests.csproj @@ -18,10 +18,10 @@ - - - - + + + + diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs index 7b12dc33e7..4610234bf6 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; using MongoDB.Driver; using Volo.Abp.MongoDB; diff --git a/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs b/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs index d0e1e76873..2e657227b1 100644 --- a/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs +++ b/modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs @@ -24,7 +24,7 @@ public abstract class MongoUserRepositoryBase : MongoDbReposi public virtual async Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .OrderBy(x => x.Id) .FirstOrDefaultAsync(u => u.UserName == userName, cancellationToken); } @@ -32,7 +32,7 @@ public abstract class MongoUserRepositoryBase : MongoDbReposi public virtual async Task> GetListAsync(IEnumerable ids, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) + return await (await GetQueryableAsync(cancellationToken)) .Where(u => ids.Contains(u.Id)) .ToListAsync(cancellationToken); } @@ -45,8 +45,8 @@ public abstract class MongoUserRepositoryBase : MongoDbReposi CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>( + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.UserName.Contains(filter) || @@ -55,16 +55,15 @@ public abstract class MongoUserRepositoryBase : MongoDbReposi (u.Surname != null && u.Surname.Contains(filter)) ) .OrderBy(sorting.IsNullOrEmpty() ? nameof(IUserData.UserName) : sorting) - .As>() - .PageBy>(skipCount, maxResultCount) + .PageBy(skipCount, maxResultCount) .ToListAsync(cancellationToken); } public async Task GetCountAsync(string filter = null, CancellationToken cancellationToken = default) { cancellationToken = GetCancellationToken(cancellationToken); - return await (await GetMongoQueryableAsync(cancellationToken)) - .WhereIf>( + return await (await GetQueryableAsync(cancellationToken)) + .WhereIf( !filter.IsNullOrWhiteSpace(), u => u.UserName.Contains(filter) || diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 4233ce7b59..e60f374566 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -155,6 +155,7 @@ $projects = ( "framework/src/Volo.Abp.BlobStoring.Minio", "framework/src/Volo.Abp.BlobStoring.Aws", "framework/src/Volo.Abp.BlobStoring.Google", + "framework/src/Volo.Abp.BlobStoring.Bunny", "framework/src/Volo.Abp.Caching", "framework/src/Volo.Abp.Caching.StackExchangeRedis", "framework/src/Volo.Abp.Castle.Core", diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index 90003189c1..ace9c878ea 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 1ec0a9e256..2623cbc7cc 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj index ec6b540a0b..eab75defd4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj index 47ef773a62..83c07df42a 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj index 8d57516f1b..78036c4c1f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 233cc474f4..d8dc1a3133 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj index db97961869..d8e572ca4f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj index 8faf21b2d3..8a5d26d1cd 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj index 0fa42f9051..09cc96eb3d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj index a98c87cdda..b66a2a7fb9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/MyProjectNameMongoDbFixture.cs b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/MyProjectNameMongoDbFixture.cs index 55387d7ba5..e69539eff8 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/MyProjectNameMongoDbFixture.cs +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/MyProjectNameMongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace MyCompanyName.MyProjectName.MongoDB; diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/Samples/SampleRepositoryTests.cs b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/Samples/SampleRepositoryTests.cs index 9f51c616ec..f705959e83 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/Samples/SampleRepositoryTests.cs +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDb/Samples/SampleRepositoryTests.cs @@ -32,7 +32,7 @@ public class SampleRepositoryTests : MyProjectNameMongoDbTestBase await WithUnitOfWorkAsync(async () => { //Act - var adminUser = await (await _appUserRepository.GetMongoQueryableAsync()) + var adminUser = await (await _appUserRepository.GetQueryableAsync()) .FirstOrDefaultAsync(u => u.UserName == "admin"); //Assert diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj index 53cc18991c..e18c27a2de 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj @@ -15,10 +15,10 @@ - - - - + + + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj index 518bf4589b..6e53e58d4e 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj index 2298178199..717900b944 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDB/MongoDbFixture.cs b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDB/MongoDbFixture.cs index 07cfd77583..63a2f598c5 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDB/MongoDbFixture.cs +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MongoDB/MongoDbFixture.cs @@ -1,5 +1,5 @@ using System; -using EphemeralMongo; +using MongoSandbox; namespace MyCompanyName.MyProjectName.MongoDB; diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj index fed5495bbe..5e43f82146 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.MongoDB.Tests/MyCompanyName.MyProjectName.MongoDB.Tests.csproj @@ -10,10 +10,10 @@ - - - - + + + +