diff --git a/Directory.Packages.props b/Directory.Packages.props index 5d9885ddbd..afcb39eb77 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -128,11 +128,11 @@ - - - - - + + + + + diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index eebf13731a..733e370c54 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -721,6 +721,8 @@ "NuGetApiKey": "NuGet API key", "QuestionCount": "Question Count", "MakeAnnouncement": "Make Announcement", - "MakeAnnouncementInfo": "Check it if you want to make an announcement for this post" + "MakeAnnouncementInfo": "Check it if you want to make an announcement for this post", + "Permission:ViewCounts": "View counts", + "ReadCount": "Read Count" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index f5fea908de..082f607eb0 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -534,7 +534,7 @@ "WhenShouldIRenewMyLicenseExplanation2": "{0} for Team Licenses;", "WhenShouldIRenewMyLicenseExplanation3": "{0} for Business and Enterprise Licenses;", "WhenShouldIRenewMyLicenseExplanation4": "However, if you renew your license more than {0} days after the expiry date, the renewal price will be the same as the initial purchase price of the license, with no discounts applied to your renewal.", - "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.", + "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.
The renewals (manual) are non-refundable. On the other hand, all subscription auto-renewals are non-refundable after 10 calendar days from the auto-renewal date. If you don't wish to continue your license, it is your responsibility to manage the renewal settings and cancel the subscription before the automatic renewal date.", "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.", @@ -1425,7 +1425,7 @@ "TotalDevelopers": "Total {0} developer(s)", "CustomPurchaseExplanation": "Tailored to your specific needs", "WhereDidYouHearAboutUs": "Where did you hear about us?", - "Twitter": "Twitter", + "Twitter": "Twitter (X)", "Facebook": "Facebook", "Youtube": "YouTube", "Google": "Google", @@ -1891,6 +1891,9 @@ "CreatePostSEOTitleInfo": "SEO URL is a clean, readable, keyword-rich URL that helps both users and search engines understand what this post is about. Keep it short with 60 characters. SEO titles over 60 characters will be truncated. Use hyphens (-) to separate words (not underscores). Include target keywords near the start. Lowercase only. No stop words unless needed (e.g: \"and\", \"or\", \"the\").", "SEOTitle": "SEO URL", "InvalidYouTubeUrl": "The URL you entered is not a valid YouTube video link. Please make sure it points to a specific video and try again.", - "SelectAnOption": "Select an option" + "SelectAnOption": "Select an option", + "MostPopular": "Most Popular", + "AnnouncmentsPageTitle": "ABP Community Announcements | Stay Updated with the Latest News", + "AnnouncmentsPageDescription": "Get the latest news, feature updates, release notes, and important announcements about the ABP framework and .NET ecosystem. Stay ahead with timely information directly from the ABP team." } } diff --git a/common.props b/common.props index 001918b484..bf99a29ca2 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 9.3.0-rc.3 - 4.3.0-rc.3 + 10.0.0-preview + 5.0.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/POST.md b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/POST.md new file mode 100644 index 0000000000..08c375eeb3 --- /dev/null +++ b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/POST.md @@ -0,0 +1,203 @@ +# ABP Platform 9.3 RC Has Been Released + +We are happy to release [ABP](https://abp.io) version **9.3 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.3! Thanks to you in advance. + +## Get Started with the 9.3 RC + +You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli). + +By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI: + +![studio-switch-to-preview.png](studio-switch-to-preview.png) + +## Migration Guide + +There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v9.2 or earlier: [ABP Version 9.3 Migration Guide](https://abp.io/docs/9.3/release-info/migration-guides/abp-9-3) + +## What's New with ABP v9.3? + +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: + +* Cron Expression Support for Background Workers +* Docs Module: PDF Export +* Angular UI: Standalone Package Structure +* Upgraded to Blazorise v1.7.7 +* Audit Logging Module: Excel Export + +### Cron Expression Support for Background Workers + +We've enhanced the [Background Workers System](https://abp.io/docs/9.3/framework/infrastructure/background-workers) by adding support for Cron expressions when using [Hangfire](https://abp.io/docs/9.3/framework/infrastructure/background-workers/hangfire) or [Quartz](https://abp.io/docs/9.3/framework/infrastructure/background-workers/quartz) as the background worker manager. This new feature provides more flexibility in scheduling background tasks compared to the simple period-based timing system. + +Now you can define complex scheduling patterns using standard Cron expressions. For example, you can schedule a task to run: "Every day at midnight", "Every Monday at 9 AM", or "First day of every month". + +Here's how you can use it in your background worker: + +```csharp +public class MyPeriodicBackgroundWorker : AsyncPeriodicBackgroundWorkerBase +{ + public MyPeriodicBackgroundWorker( + AbpAsyncTimer timer, + IServiceScopeFactory serviceScopeFactory) + : base(timer, serviceScopeFactory) + { + // You can either use Period for simple intervals + Timer.Period = 600000; //10 minutes + + // 👇 or use CronExpression for more complex scheduling 👇 + CronExpression = "0 0/10 * * * ?"; //Run every 10 minutes + } + + protected async override Task DoWorkAsync( + PeriodicBackgroundWorkerContext context) + { + // Your background work... + } +} +``` + +The `CronExpression` property takes precedence over the `Period` property when both are set. This feature is available when you use either the [Hangfire](https://abp.io/docs/9.3/framework/infrastructure/background-workers/hangfire) or [Quartz](https://abp.io/docs/9.3/framework/infrastructure/background-workers/quartz) background worker managers. + +> See the [Background Workers documentation](https://abp.io/docs/9.3/framework/infrastructure/background-workers) for more information about configuring and using background workers with Cron expressions. + +### Docs Module: PDF Export + +We're excited to introduce a new feature in the Docs Module that allows users to export documentation as PDF files. This feature makes it easier for users to access documentation offline or share it with team members who might not have immediate access to the online documentation system. + +**Administrators can generate PDF files from the back-office side**: + +![PDF generation settings in the admin side](generate-pdf-docs.png) + +and **then a "Download PDF" button appears in the document system** (as shown in the image below - the bottom right of the navigation menu -), allowing users to download the compiled documentation as a PDF file: + +![Download PDF button in the documentation system](download-pdf-on-docs.png) + +The feature supports multiple versions of documentation, different language variants, and ensures proper formatting of all content including code blocks and technical documentation. + +### Angular UI: Standalone Package Structure + +ABP v9.3 introduces support for Angular's standalone components architecture while maintaining **full compatibility with existing module-based applications**. This update aligns with Angular's strategic direction toward standalone components as the recommended approach for building Angular applications. + +The key improvements include: + +* **Dual-support routing configurations** that work seamlessly with both module-based and standalone approaches +* **ABP Suite integration** for generating code that supports standalone components +* **Updated schematics** that provide templates for both development patterns + +This enhancement gives developers the flexibility to choose their preferred Angular architecture. Existing module-based applications **continue to work without modifications**, while new projects can leverage the standalone approach for simplified dependency management, reduced boilerplate code, and better lazy-loading capabilities. + +> For developers interested in migrating to standalone components or starting new projects, we'll be publishing a comprehensive blog post with detailed guidance and best practices. In the meantime, you can check [#22829](https://github.com/abpframework/abp/pull/22829) for implementation details of the standalone package structure and make the necessary changes to your project. + +### Upgraded to Blazorise v1.7.7 + +Upgraded the [Blazorise](https://blazorise.com/) library to v1.7.7 for Blazor UI. If you are upgrading your project to v9.3.0, please ensure that all the Blazorise-related packages are using v1.7.7 in your application. Otherwise, you might get errors due to incompatible versions. + +> See [#23013](https://github.com/abpframework/abp/pull/23013) for the updated NuGet packages. + +### Audit Logging Module: Excel Export + +In this version, we've added Excel export capabilities to the [Audit Logging Module](https://abp.io/docs/latest/modules/audit-logging-pro), allowing administrators to export audit logs and entity changes to Excel files for further analysis or reporting purposes. + +![](audit-logs-export-to-excel.png) + +This feature enables users to: + +- Export audit logs with filtering options +- Export entity changes with detailed information +- Receive email notifications when exports are completed or fail +- Download exported files via secure links + +The export process runs in the background, and once completed, users receive an email with a download link. This approach ensures that even large audit log exports don't block the UI or time out during processing. + +You can configure various aspects of this feature using the `AuditLogExcelFileOptions` in your module's configuration: + +```csharp +Configure(options => +{ + // How long to keep exported files before cleanup + options.FileRetentionHours = 48; + + // Base URL for download links in notification emails + options.DownloadBaseUrl = "https://yourdomain.com"; + + // Configure the cleanup worker schedule + options.ExcelFileCleanupOptions.Period = (int)TimeSpan.FromHours(24).TotalMilliseconds; + + // Use cron expression for more advanced scheduling (requires Hangfire or Quartz) + options.ExcelFileCleanupOptions.CronExpression = "0 2 * * *"; // Run at 2 AM daily +}); +``` + +The module includes pre-configured email templates for notifications about completed or failed exports, ensuring users are always informed about the status of their export requests. + +> **Note**: This feature requires a configured BLOB storage provider to store the generated Excel files. See the [BLOB Storing documentation](https://abp.io/docs/9.3/framework/infrastructure/blob-storing) for more information. + +For more details about the Audit Logging Module and its Excel export capabilities, please refer to the [official documentation](https://abp.io/docs/9.3/modules/audit-logging-pro). + +## Community News + +### Announcing ABP Studio 1.0 General Availability 🚀 + +![](abp-studio.png) + +We are thrilled to announce that ABP Studio has reached version 1.0 and is now generally available! This marks a significant milestone for our integrated development environment designed specifically for ABP developers. The stable release brings several powerful features including: + +* Enhanced Solution Runner with health monitoring capabilities +* Theme style selection during project creation (Basic, LeptonX Lite, and LeptonX Themes) +* New "Container" application type for better Docker container management +* Improved handling of multiple DbContexts for migration operations + +> For a detailed overview of these features and to learn more about what's coming next, check out our [announcement post](https://abp.io/community/articles/announcing-abp-studio-1-0-general-availability-82yw62bt). + +### ABP Community Talks 2025.05: Empower Elsa Workflows with AI in .NET + ABP Framework + +In this episode of ABP Community Talks, 2025.05, we are thrilled to host [**Sipke Schoorstra**](https://github.com/sfmskywalker), the creator of the [Elsa Workflows](https://docs.elsaworkflows.io/) library! This month's session is all about **"Empower Elsa Workflows with AI in .NET + ABP Framework"**. + +![](community-talk-2025-5.png) + +Sipke will join us to demonstrate how you can leverage AI within Elsa Workflows using .NET and the ABP Framework. The session will explore practical techniques and showcase how to integrate AI capabilities to enhance and automate your business processes within the Elsa workflow engine. + +> 👉 Don't miss this opportunity to learn directly from the creator of Elsa and see real-world examples of building intelligent, automated workflows! You can register from [here](https://kommunity.com/volosoft/events/abp-community-talks-202505empower-elsa-workflows-with-ai-in-netabp-framework-3965dd32). + +### ABP Bootcamp: Mastering Infrastructure & Features + +We are excited to announce the very first **ABP Bootcamp: Mastering Infrastructure & Features**! This is a live training program designed to give you hands-on, practical experience with ABP's core infrastructure and features. + +![ABP Bootcamp: Mastering Infrastructure & Features](bootcamp.png) + +Join the ABP Bootcamp to learn directly from the core team in a focused, hands-on program designed for busy developers. Over four days, you'll gain a deep understanding of ABP's infrastructure, best practices, and practical skills you can immediately apply to your projects. + +> **Seats are limited!** Don't miss this opportunity to level up your ABP skills with direct guidance from the experts. +> +> 👉 [See full details and reserve your seat!](https://abp.io/bootcamp) + +### New ABP Community Articles + +There are exciting articles contributed by the ABP community as always. I will highlight some of them here: + +* [Prabhjot Singh](https://abp.io/community/members/prabhjot) has published 3 new articles: + * [Accessing Multiple Remote ABP based Backends Using HttpApi.Client](https://abp.io/community/articles/consume-multi-backends-using-clients-6f4vcggh) + * [Adopting the new .slnx format to organize applications and services](https://abp.io/community/articles/adopting-the-new-.slnx-format-to-organize-applications-6cm3vl8k) + * [Replacing Dynamic client proxies with Static client proxies](https://abp.io/community/articles/replacing-dynamic-client-proxies-with-static-client-proxies-g30lf0vx) +* [Liming Ma](https://github.com/maliming) has published 2 new articles: + * [Resolving Tenant from Route in ABP Framework](https://abp.io/community/articles/resolving-tenant-from-route-in-abp-framework-ah7oru97) + * [Integrating .NET AI Chat Template with ABP Framework](https://abp.io/community/articles/integrating-.net-ai-chat-template-with-abp-framework-qavb5p2j) +* [Engincan Veske](https://engincanveske.substack.com/) has published 2 new articles: + * [Understanding HttpApi.Client Project & Remote Services in an ABP Based Application](https://abp.io/community/articles/http-api-client-and-remote-services-in-abp-based-application-xkknsp6m) + * [Using Elsa 3 with the ABP Framework: A Comprehensive Guide](https://abp.io/community/articles/using-elsa-3-workflow-with-abp-framework-usqk8afg) +* [Enis Necipoğlu](https://github.com/enisn) has published 2 new articles: + * [White Labeling in ABP Framework](https://abp.io/community/articles/white-labeling-in-abp-framework-5trwmrfm) by [Enis Necipoğlu](https://github.com/enisn) + * [You do it wrong! Customizing ABP Login Page Correctly](https://abp.io/community/articles/you-do-it-wrong-customizing-abp-login-page-correctly-bna7wzt5) +* [New in ABP Studio: Docker Container Management](https://abp.io/community/articles/abp-studio-docker-container-management-ex7r27y8) by [Yunus Emre Kalkan](https://github.com/yekalkan) +* [Solving MongoDB GUID Issues After an ABP Framework Upgrade](https://abp.io/community/articles/solving-mongodb-guid-issues-after-an-abp-framework-upgrade-tv8waw1n) by [Burak Demir](https://abp.io/community/members/burakdemir) + + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. + +## Conclusion + +This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/9.3/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v9.3 RC and provide feedback to help us release a more stable version. + +Thanks for being a part of this community! \ No newline at end of file diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/abp-studio.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/abp-studio.png new file mode 100644 index 0000000000..76b24a8573 Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/abp-studio.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/audit-logs-export-to-excel.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/audit-logs-export-to-excel.png new file mode 100644 index 0000000000..4c10f516db Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/audit-logs-export-to-excel.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/bootcamp.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/bootcamp.png new file mode 100644 index 0000000000..e07293c13c Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/bootcamp.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/community-talk-2025-5.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/community-talk-2025-5.png new file mode 100644 index 0000000000..3989dde47a Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/community-talk-2025-5.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/cover-image.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/cover-image.png new file mode 100644 index 0000000000..291da0d04f Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/cover-image.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/download-pdf-on-docs.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/download-pdf-on-docs.png new file mode 100644 index 0000000000..ce5b1b3727 Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/download-pdf-on-docs.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/generate-pdf-docs.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/generate-pdf-docs.png new file mode 100644 index 0000000000..2a1de4398d Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/generate-pdf-docs.png differ diff --git a/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/studio-switch-to-preview.png b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/studio-switch-to-preview.png new file mode 100644 index 0000000000..32f6d01edb Binary files /dev/null and b/docs/en/Blog-Posts/2025-06-18 v9_3_Preview/studio-switch-to-preview.png differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/1752664190317-min.jpeg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/1752664190317-min.jpeg new file mode 100644 index 0000000000..de317b4647 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/1752664190317-min.jpeg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15924-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15924-min.jpg new file mode 100644 index 0000000000..3730feed30 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15924-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15933-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15933-min.jpg new file mode 100644 index 0000000000..5c4348c2a6 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15933-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15934-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15934-min.jpg new file mode 100644 index 0000000000..bfaec7bc9d Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15934-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15941-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15941-min.jpg new file mode 100644 index 0000000000..fe52494de8 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15941-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15944-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15944-min.jpg new file mode 100644 index 0000000000..2692dd576d Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15944-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15946-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15946-min.jpg new file mode 100644 index 0000000000..da423629c7 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15946-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15947-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15947-min.jpg new file mode 100644 index 0000000000..f3f6350c09 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15947-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15948-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15948-min.jpg new file mode 100644 index 0000000000..c875ad61e3 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15948-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15949-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15949-min.jpg new file mode 100644 index 0000000000..69840520e0 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15949-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15956-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15956-min.jpg new file mode 100644 index 0000000000..73ef867711 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15956-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15959-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15959-min.jpg new file mode 100644 index 0000000000..fb6d3f520c Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15959-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15963-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15963-min.jpg new file mode 100644 index 0000000000..a9595acaa9 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15963-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15964-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15964-min.jpg new file mode 100644 index 0000000000..4ed6c26b33 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15964-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15966-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15966-min.jpg new file mode 100644 index 0000000000..80296b7ead Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15966-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15968-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15968-min.jpg new file mode 100644 index 0000000000..6f915ee94c Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15968-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15969-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15969-min.jpg new file mode 100644 index 0000000000..de112a11ea Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15969-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15970-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15970-min.jpg new file mode 100644 index 0000000000..acdd4bb0b0 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15970-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15971-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15971-min.JPG new file mode 100644 index 0000000000..5fd55d58e1 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15971-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15972-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15972-min.JPG new file mode 100644 index 0000000000..c1956daebd Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15972-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15973-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15973-min.JPG new file mode 100644 index 0000000000..3d1a5b0789 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15973-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15974-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15974-min.JPG new file mode 100644 index 0000000000..147f7ef967 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15974-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15975-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15975-min.JPG new file mode 100644 index 0000000000..173610a7ec Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15975-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15976-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15976-min.JPG new file mode 100644 index 0000000000..c48a7944d6 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15976-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15977-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15977-min.JPG new file mode 100644 index 0000000000..6f5cb19831 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15977-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15979-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15979-min.JPG new file mode 100644 index 0000000000..9ca4140717 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15979-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15980-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15980-min.JPG new file mode 100644 index 0000000000..0ebf59d8b8 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15980-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15981-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15981-min.JPG new file mode 100644 index 0000000000..10e02621cf Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15981-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15982-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15982-min.JPG new file mode 100644 index 0000000000..63bc90cc76 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15982-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15983-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15983-min.JPG new file mode 100644 index 0000000000..14e1f16c9a Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15983-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15984-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15984-min.jpg new file mode 100644 index 0000000000..0984be8e2e Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15984-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15985-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15985-min.JPG new file mode 100644 index 0000000000..8d30f62cd3 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15985-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15986-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15986-min.JPG new file mode 100644 index 0000000000..55f48e13a9 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15986-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15987-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15987-min.JPG new file mode 100644 index 0000000000..9cbd57efaa Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15987-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15989-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15989-min.jpg new file mode 100644 index 0000000000..f4c0bb2cfc Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15989-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15994-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15994-min.jpg new file mode 100644 index 0000000000..35201fee15 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15994-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15995-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15995-min.jpg new file mode 100644 index 0000000000..60546b5c9f Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15995-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15996-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15996-min.jpg new file mode 100644 index 0000000000..147babb883 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15996-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15998-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15998-min.jpg new file mode 100644 index 0000000000..7c2d080da7 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15998-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15999-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15999-min.jpg new file mode 100644 index 0000000000..0cf89bc0e4 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_15999-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16001-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16001-min.jpg new file mode 100644 index 0000000000..1f8a500948 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16001-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16002-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16002-min.jpg new file mode 100644 index 0000000000..8753a09cfe Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16002-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16003-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16003-min.jpg new file mode 100644 index 0000000000..4f4eec7b54 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16003-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16006-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16006-min.JPG new file mode 100644 index 0000000000..eef991dd38 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16006-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16007-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16007-min.jpg new file mode 100644 index 0000000000..48cb26cd40 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16007-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16008-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16008-min.jpg new file mode 100644 index 0000000000..3eadec4490 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16008-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16009-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16009-min.jpg new file mode 100644 index 0000000000..0e8387065d Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16009-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16011-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16011-min.jpg new file mode 100644 index 0000000000..c96d3a8d37 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16011-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16012-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16012-min.jpg new file mode 100644 index 0000000000..62a16ac428 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16012-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16013-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16013-min.jpg new file mode 100644 index 0000000000..256c2bc22c Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16013-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16019-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16019-min.JPG new file mode 100644 index 0000000000..e657a682ad Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16019-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16021-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16021-min.JPG new file mode 100644 index 0000000000..5a81fddee1 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16021-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16022-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16022-min.JPG new file mode 100644 index 0000000000..ed8660fdbd Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16022-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16023-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16023-min.JPG new file mode 100644 index 0000000000..05b2040d18 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16023-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16024-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16024-min.JPG new file mode 100644 index 0000000000..2cc6f9fbee Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16024-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16025-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16025-min.JPG new file mode 100644 index 0000000000..0ede12db76 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16025-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16026-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16026-min.JPG new file mode 100644 index 0000000000..268e4a4454 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16026-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16027-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16027-min.JPG new file mode 100644 index 0000000000..68d288fc19 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16027-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16028-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16028-min.JPG new file mode 100644 index 0000000000..72d060a6e1 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16028-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16029-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16029-min.JPG new file mode 100644 index 0000000000..47bc5a910c Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16029-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16030-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16030-min.JPG new file mode 100644 index 0000000000..5180e62195 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16030-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16031-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16031-min.JPG new file mode 100644 index 0000000000..3085d54f3d Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16031-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16032-min.jpg b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16032-min.jpg new file mode 100644 index 0000000000..26ddb2dc75 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16032-min.jpg differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16040-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16040-min.JPG new file mode 100644 index 0000000000..4bf5c04712 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16040-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16041-min.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16041-min.JPG new file mode 100644 index 0000000000..3b38f0c487 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/IMG_16041-min.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/cover.png b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/cover.png new file mode 100644 index 0000000000..946fdc2ab0 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/cover.png differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/image-20250722203102576.png b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/image-20250722203102576.png new file mode 100644 index 0000000000..175fd45289 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/image-20250722203102576.png differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-1.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-1.JPG new file mode 100644 index 0000000000..6be6b041d3 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-1.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-2.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-2.JPG new file mode 100644 index 0000000000..b05c488c4d Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-2.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-3.JPG b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-3.JPG new file mode 100644 index 0000000000..51dd5e7e57 Binary files /dev/null and b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/my-talk-3.JPG differ diff --git a/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/post.md b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/post.md new file mode 100644 index 0000000000..1b996e4867 --- /dev/null +++ b/docs/en/Blog-Posts/2025-07-22-My-Impressionf-at-WeAreDevelopers/post.md @@ -0,0 +1,101 @@ +# WeAreDevelopers 2025: A Speaker’s Impressions + +![Conference Opening](IMG_15924-min.jpg) + +After speaking at DotNext Moscow, I had high expectations for WeAreDevelopers 2025—and the event delivered on all fronts. Held in Berlin / Germany, it brought together a truly global crowd of developers, tech leaders, and innovators. As a speaker and software architect , I’m sharing my first-hand highlights, favorite moments, and candid scenes from this 2025’s conference. + +## 🗣 My Talk + +We have a good experience on multi-tenancy topic in SaaS development. My talk's topic was "Building Multi-Tenant ASP.NET Core Applications: Best Practices and Real-World Solutions". It was on the stage 4, 11 July Friday 10:20 am - 10:50 am and [this my presentation file](https://github.com/ebicoglu/presentations/blob/main/multi-tenancy-wearedevelopers-2025_30mins.pptx). + +![My Talk Info](image-20250722203102576.png) + +![Pictures from my talk](my-talk-1.JPG) +![Pictures from my talk](my-talk-2.JPG) +![Pictures from my talk](my-talk-3.JPG) + + +## 🏛 Huge Venue + +![Main Stage](1752664190317-min.jpeg) +*The image is credited to WeAreDevelopers organization* + +First of all, I had been in numerous software conferences, I must say that I've never seen such a big software event. The event spanned **500+ sessions across 20+ stages**, including the HR Leaders Summit for **2 full days**. + +![Main Stage2](IMG_15933-min.jpg) +![Crowd Energy](IMG_15944-min.jpg) + +------ + +## 🎤 Opening Keynote from GitHub + +GitHub CEO Thomas Dohmke initiated the conference on the main stage with a talk on *“Agents for the Sake of Happiness”*. Having introduced Copilot three years ago here, he now launched bold predictions about autonomous AI‍—a fascinating evolution... He demonstrated GitHub Co-Pilot's AI and created a snake game. Altough it didn't work as he planned, we're developers we know live coding is hard. Actually that's because we shouldn't rely on AI. AI is not deterministic even though we set all those temperature, TopP, TopK parameters to minimum. + +> AI is a good but not trustable friend! + +![Thomas Dohmke on Stage](IMG_15941-min.jpg) + +------ + +## 🧭 11 Parallel Stages: Rush + +There were 11 stages where 11 different topics were being explained. And the sessions were 30 minutes. Actually that's the downside of this event. Because there were so nice talks that needs to be minimum 40 minutes. But anyway I understand the organization team because there are many smart speakers whose needs to be included in this event. So as a attendee I was on a hurry to pick the next talk even when I was listening to a talk :) + +The venue consists of 3 buildings. So if you pick a talk on another building, you have 10 mins to go to toilet or drink something and catch the next session on that far building... + +There was HR track with **3 stages and 2 full days** of HR/Talent Acquisition programming, it attracted a notable overlap of developers and HR pros. Themes included AI‑powered recruiting, remote work culture, mental health, diversity & inclusion, and building AI agents + +![Fireside Chat](IMG_15949-min.jpg) +![Panel Discussion](IMG_15948-min.jpg) + + +------ + +## 🤖 AI & AI & AI & Others... + +I'm one of those AI lovers. I love learning cutting-edge information. And as I see AI is being more trendy everyday. That's why most of the talks were about AI. Everything related to AI. I generaly attended AI related talks because I'm also working on some AI topics in Volosoft at the moment. + + +------ + +## 🤝 Expo Floor & Networking + +The expo was a developer’s playground—cloud services, open‑source tools, startups, and enterprise platforms. I found new partners and reconnected with peers in a buzzing atmosphere. Everywhere was full of talking's even outside. If you want to get fresh air and drink coffee, you can go out and listen to the outside talks. + +![Expo Hall](IMG_15956-min.jpg) +![Booth Visit](IMG_15959-min.jpg) + + + +Networking wasn't just daytime chatter—hallway meetups and evening socials were unforgettable. + +![Networking Moments](IMG_15964-min.jpg) +![After Hours](IMG_15972-min.JPG) + +------ + +## 😂 Candid & Fun Moments + +Swag stations, sponsor games, “developer selfies”—these lighter moments kept the vibe upbeat and human. + +![Fun Moment](IMG_15971-min.JPG) +![Developer Selfie](IMG_15980-min.JPG) + +------ + +## ✅ Final Thoughts & Looking Ahead + +WeAreDevelopers 2025 was an unforgettable three-day ride: **15,000 tech minds**, **500+ sessions**, and a true **bridge between developers and HR** +I’m leaving with: + +- Fresh strategies in GenAI and SaaS growth +- Stronger HR-tech understanding and crossover potential +- New professional connections—and fun memories + + +------ + +![Conference Wrap-Up](IMG_15999-min.jpg) + + + diff --git a/docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/POST.md b/docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/POST.md new file mode 100644 index 0000000000..4fcb9860bc --- /dev/null +++ b/docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/POST.md @@ -0,0 +1,250 @@ +# Using Hangfire Dashboard in ABP API Website 🚀 + +## Introduction + +In this article, I'll show you how to integrate and use the Hangfire Dashboard in an ABP API website. + +Typically, API websites use `JWT Bearer` authentication, but the Hangfire Dashboard isn't compatible with `JWT Bearer` authentication. Therefore, we need to implement `Cookies` and `OpenIdConnect` authentication for the Hangfire Dashboard access. + +## Creating a New ABP Demo Project 🛠️ + +We'll create a new ABP Demo `Tiered` project that includes `AuthServer`, `API`, and `Web` projects. + +```bash +abp new AbpHangfireDemoApp -t app --tiered +``` + +Now let's add the Hangfire Dashboard to the `API` project and configure it to use `Cookies` and `OpenIdConnect` authentication for accessing the dashboard. + +## Adding a New Hangfire Application 🔧 + +We need to add a new Hangfire application to the `appsettings.json` file in the `DbMigrator` project: + +> **Note:** Replace `44371` with your `API` project's port. + +```json +"OpenIddict": { + "Applications": { + //... + "AbpHangfireDemoApp_Hangfire": { + "ClientId": "AbpHangfireDemoApp_Hangfire", + "RootUrl": "https://localhost:44371/" + } + //... + } +} +``` + +2. Update the `OpenIddictDataSeedContributor`'s `CreateApplicationsAsync` method in the `Domain` project to seed the new Hangfire application. + +```csharp + //Hangfire Client +var hangfireClientId = configurationSection["AbpHangfireDemoApp_Hangfire:ClientId"]; +if (!hangfireClientId.IsNullOrWhiteSpace()) +{ + var hangfireClientRootUrl = configurationSection["AbpHangfireDemoApp_Hangfire:RootUrl"]!.EnsureEndsWith('/'); + + await CreateApplicationAsync( + applicationType: OpenIddictConstants.ApplicationTypes.Web, + name: hangfireClientId!, + type: OpenIddictConstants.ClientTypes.Confidential, + consentType: OpenIddictConstants.ConsentTypes.Implicit, + displayName: "Hangfire Application", + secret: configurationSection["AbpHangfireDemoApp_Hangfire:ClientSecret"] ?? "1q2w3e*", + grantTypes: new List //Hybrid flow + { + OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit + }, + scopes: commonScopes, + redirectUris: new List { $"{hangfireClientRootUrl}signin-oidc" }, + postLogoutRedirectUris: new List { $"{hangfireClientRootUrl}signout-callback-oidc" }, + clientUri: hangfireClientRootUrl, + logoUri: "/images/clients/aspnetcore.svg" + ); +} +``` + +3. Run the `DbMigrator` project to seed the new Hangfire application. + +### Adding Hangfire Dashboard to the `API` Project 📦 + +1. Add the following packages and modules dependencies to the `API` project: + +```bash + + + +``` + +```cs +typeof(AbpBackgroundJobsHangfireModule), +typeof(AbpAspNetCoreAuthenticationOpenIdConnectModule) +``` + +2. Add the `HangfireClientId` and `HangfireClientSecret` to the `appsettings.json` file in the `API` project: + +```csharp +"AuthServer": { + "Authority": "https://localhost:44358", + "RequireHttpsMetadata": true, + "MetaAddress": "https://localhost:44358", + "SwaggerClientId": "AbpHangfireDemoApp_Swagger", + "HangfireClientId": "AbpHangfireDemoApp_Hangfire", + "HangfireClientSecret": "1q2w3e*" +} +``` + +3. Add the `ConfigureHangfire` method to the `API` project to configure Hangfire: + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + //... + + //Add Hangfire + ConfigureHangfire(context, configuration); + //... +} + +private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) +{ + context.Services.AddHangfire(config => + { + config.UseSqlServerStorage(configuration.GetConnectionString("Default")); + }); +} +``` + +4. Modify the `ConfigureAuthentication` method to add new `Cookies` and `OpenIdConnect` authentication schemes: + +```csharp +private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) +{ + context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddAbpJwtBearer(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = configuration.GetValue("AuthServer:RequireHttpsMetadata"); + options.Audience = "AbpHangfireDemoApp"; + + options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase) + ? CookieAuthenticationDefaults.AuthenticationScheme + : null; + }) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) + .AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); + options.ResponseType = OpenIdConnectResponseType.Code; + + options.ClientId = configuration["AuthServer:HangfireClientId"]; + options.ClientSecret = configuration["AuthServer:HangfireClientSecret"]; + + options.UsePkce = true; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + options.Scope.Add("roles"); + options.Scope.Add("email"); + options.Scope.Add("phone"); + options.Scope.Add("AbpHangfireDemoApp"); + + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }); + + //... +} +``` + +5. Add a custom middleware and `UseAbpHangfireDashboard` after `UseAuthorization` in the `OnApplicationInitialization` method: + +```csharp +//... +app.UseAuthorization(); + +app.Use(async (httpContext, next) => +{ + if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)) + { + var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + if (!authenticateResult.Succeeded) + { + await httpContext.ChallengeAsync( + OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties + { + RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString + }); + return; + } + } + await next.Invoke(); +}); +app.UseAbpHangfireDashboard("/hangfire", options => +{ + options.AsyncAuthorization = new[] + { + new AbpHangfireAuthorizationFilter() + }; +}); + +//... +``` + +Perfect! 🎉 Now you can run the `AuthServer` and `API` projects and access the Hangfire Dashboard at `https://localhost:44371/hangfire`. + +> **Note:** Replace `44371` with your `API` project's port. + +The first time you access the Hangfire Dashboard, you'll be redirected to the login page of the `AuthServer` project. After you log in, you'll be redirected back to the Hangfire Dashboard. + +![Hangfire Dashboard](gif.gif) + +## Key Points 🔑 + +### 1. Authentication Scheme Selection + +The default authentication scheme in API websites is `JWT Bearer`. We've implemented `Cookies` and `OpenIdConnect` specifically for the Hangfire Dashboard. + +We've configured the `JwtBearerOptions`'s `ForwardDefaultSelector` to use `CookieAuthenticationDefaults.AuthenticationScheme` for Hangfire Dashboard requests. + +This means that if the request path starts with `/hangfire`, the request will be authenticated using the `Cookies` authentication scheme; otherwise, it will use the `JwtBearer` authentication scheme. + +```csharp +options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase) + ? CookieAuthenticationDefaults.AuthenticationScheme + : null; +``` + +### 2. Custom Middleware for Authentication + +We've also implemented a custom middleware to handle `Cookies` authentication for the Hangfire Dashboard. If the current request isn't authenticated with the `Cookies` authentication scheme, it will be redirected to the login page. + +```csharp +app.Use(async (httpContext, next) => +{ + if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)) + { + var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + if (!authenticateResult.Succeeded) + { + await httpContext.ChallengeAsync( + OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties + { + RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString + }); + return; + } + } + await next.Invoke(); +}); +``` + +## References 📚 + +- [ABP Hangfire Background Job Manager](https://abp.io/docs/latest/framework/infrastructure/background-jobs/hangfire) +- [Use cookie authentication in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-9.0) diff --git a/docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/gif.gif b/docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/gif.gif new file mode 100644 index 0000000000..e4fa879eb4 Binary files /dev/null and b/docs/en/Community-Articles/2025-06-20-Using-Hangfire-Dashboard-in-ABP-API-website/gif.gif differ diff --git a/docs/en/Community-Articles/2025-07-17-summar-campaign/post.md b/docs/en/Community-Articles/2025-07-17-summar-campaign/post.md new file mode 100644 index 0000000000..d1ac596c29 --- /dev/null +++ b/docs/en/Community-Articles/2025-07-17-summar-campaign/post.md @@ -0,0 +1,28 @@ +**It is going to get hotter with ABP’s Summer Campaign!** + +Since it’s summer time, we wanted to make it even hotter by announcing a summer campaign! From July 21 to 31 we are offering a 20% discount on all ABP licenses. Now is the best time to invest in ABP and start developing asp net applications faster without wasting your time with repetitive tasks. + +## Summer Campaign Terms + +Please review the following terms and conditions carefully. + +* This offer is available for extensions and new purchases. +* Developer seat purchases are also included to the campaign. +* Campaign is available from July 21st to July 31st. +* Discounts are valid on selected licenses only. +* This offer cannot be combined with other promotions or discounts. + +**Why Choose ABP?** + +ABP offers a powerful infrastructure, simplifying modern ASP.NET core development. It helps develop modern ASP.NET applications, including ASP.NET core MVC web applications, blazor front-end projects, and angular .NET Core solutions. + +-The core framework and pre-built modules are designed with microservice architecture in mind. +-ABP provides a module system that allows you to develop reusable application modules. +-Helps implement a DDD based layered architecture and build a maintainable code base. +-Easily manage SaaS applications with integrated multi-tenancy, from database to UI. + +**This Offer Ends July 31, So Hurry Up!** + +This summer campaign is running from July 21 to July 31, so don’t miss your chance. Now is the perfect opportunity to enhance your asp net web development with ABP and benefit from our exclusive features. + +Get Your Discount Now: [https://abp.io/pricing?utm_source=abpwebsite&utm_medium=referral&utm_campaign=summer25_blog](https://abp.io/pricing?utm_source=abpwebsite&utm_medium=referral&utm_campaign=summer25_blog) diff --git a/docs/en/framework/architecture/modularity/installer-projects.md b/docs/en/framework/architecture/modularity/installer-projects.md new file mode 100644 index 0000000000..81c75ab015 --- /dev/null +++ b/docs/en/framework/architecture/modularity/installer-projects.md @@ -0,0 +1,221 @@ +# Module Installer Projects + +Each ABP module includes an `.Installer` project (e.g., `Volo.Abp.Account.Installer`) that serves as a **Virtual File System container** for module installation and resource management. These projects are essential for the ABP CLI to understand and install modules properly. + +## Purpose of Installer Projects + +Installer projects have three main purposes: + +1. **Virtual File System Integration**: Register the module's embedded resources with ABP's Virtual File System +2. **Resource Packaging**: Package module metadata files (`.abpmdl` and `.abppkg`) as embedded resources +3. **CLI Integration**: Enable the ABP CLI to understand module structure and install modules automatically + +## Structure of Installer Projects + +### Project Files + +- **`{ModuleName}.Installer.csproj`**: References `Volo.Abp.VirtualFileSystem` and embeds module metadata files +- **`InstallationNotes.md`**: Documentation for the module +- **`Volo/Abp/{ModuleName}/Abp{ModuleName}InstallerModule.cs`**: The core module class that registers embedded resources + +### Example Installer Module + +```csharp +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.Account; + +[DependsOn(typeof(AbpVirtualFileSystemModule))] +public class AbpAccountInstallerModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + } +} +``` + +### Project Configuration + +The `.csproj` file embeds module metadata as content: + +```xml + + + net9.0 + true + + + + + + + + + + + true + content\ + + + + + true + content\ + + + +``` + +## Module Metadata Files + +### `.abpmdl` (Module Definition) + +The module definition file describes the module's structure and packages: + +```json +{ + "folders": { + "items": { + "src": {}, + "test": {} + } + }, + "packages": { + "Volo.Abp.Account.Web": { + "path": "src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.abppkg", + "folder": "src" + }, + "Volo.Abp.Account.Application": { + "path": "src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg", + "folder": "src" + } + } +} +``` + +### `.abppkg` (Package Definition) + +Each package has a definition file that specifies its role: + +```json +{ + "role": "lib.application" +} +``` + +Common roles: +- `lib.application`: Application layer package +- `lib.mvc`: MVC/Web layer package +- `lib.domain`: Domain layer package +- `lib.domain-shared`: Shared domain layer package +- `lib.efcore`: Entity Framework Core package + +## How Installer Projects Work + +### 1. CLI Installation Process + +When you run `abp add-module Volo.Abp.Account`: + +1. **Download Installer Package**: CLI downloads `Volo.Abp.Account.Installer` from NuGet +2. **Read Module Definition**: CLI reads the embedded `.abpmdl` file to understand module structure +3. **Read Package Definitions**: CLI reads `.abppkg` files to understand package roles +4. **Install Packages**: CLI installs appropriate packages to correct project types based on roles +5. **Add Dependencies**: CLI adds module dependencies to project module classes + +### 2. Virtual File System Integration + +The `InstallerModule` registers itself with the Virtual File System: + +```csharp +options.FileSets.AddEmbedded(); +``` + +This makes embedded resources available at runtime and enables: +- Access to module metadata +- Resource file management +- Module configuration + +## Creating Installer Projects for New Modules + +### Required Files + +1. **Project File**: `{ModuleName}.Installer.csproj` +2. **Module Class**: `Abp{ModuleName}InstallerModule.cs` +3. **Documentation**: `InstallationNotes.md` +4. **Module Definition**: `{ModuleName}.abpmdl` (in module root) +5. **Package Definitions**: `{PackageName}.abppkg` (in each package) + +### Template Structure + +``` +modules/your-module/ +├── src/ +│ ├── Volo.Abp.YourModule.Installer/ +│ │ ├── Volo.Abp.YourModule.Installer.csproj +│ │ ├── InstallationNotes.md +│ │ └── Volo/ +│ │ └── Abp/ +│ │ └── YourModule/ +│ │ └── AbpYourModuleInstallerModule.cs +│ └── [other packages]/ +├── Volo.Abp.YourModule.abpmdl +└── [other module files] +``` + +### Package Definition Examples + +For different package types: + +```json +// Application package +{ "role": "lib.application" } + +// MVC package +{ "role": "lib.mvc" } + +// Domain package +{ "role": "lib.domain" } + +// EF Core package +{ "role": "lib.efcore" } +``` + +## Why Installer Projects Appear "Empty" + +Installer projects appear minimal because their primary function is infrastructure, not business logic: + +- **No Business Logic**: Business logic belongs in the actual module packages +- **Pure Infrastructure**: They only handle module installation and resource management +- **CLI Integration**: They enable automated module installation through the ABP CLI +- **Resource Management**: They package and distribute module metadata + +## Best Practices + +1. **Follow Naming Convention**: Use `{ModuleName}.Installer` for the project name +2. **Include Documentation**: Always provide `InstallationNotes.md` with module information +3. **Proper Dependencies**: Only depend on `Volo.Abp.VirtualFileSystem` +4. **Embed All Metadata**: Include both `.abpmdl` and `.abppkg` files +5. **Test Installation**: Verify your installer works with `abp add-module` command + +## Troubleshooting + +### Common Issues + +1. **Missing .abpmdl file**: Ensure the module definition file exists in the module root +2. **Missing .abppkg files**: Each package needs a definition file +3. **Incorrect roles**: Use appropriate roles for each package type +4. **CLI not finding module**: Verify the installer package is published to NuGet + +### Verification Steps + +1. Build the installer project: `dotnet build` +2. Check embedded resources: Verify `.abpmdl` and `.abppkg` files are embedded +3. Test CLI installation: `abp add-module YourModule` +4. Verify dependencies: Check that module dependencies are added correctly + +This installer system enables ABP's sophisticated module architecture, allowing for automated installation with proper dependency resolution and project type matching. \ No newline at end of file diff --git a/docs/en/framework/architecture/multi-tenancy/index.md b/docs/en/framework/architecture/multi-tenancy/index.md index 0a035d110d..98a4c9afb5 100644 --- a/docs/en/framework/architecture/multi-tenancy/index.md +++ b/docs/en/framework/architecture/multi-tenancy/index.md @@ -380,6 +380,19 @@ namespace MultiTenancyDemo.Web * A tenant resolver should set `context.TenantIdOrName` if it can determine it. If not, just leave it as is to allow the next resolver to determine it. * `context.ServiceProvider` can be used if you need to additional services to resolve from the [dependency injection](../../fundamentals/dependency-injection.md) system. +##### The Fallback Tenant + +If you want to always fallback to a tenant (in case of no tenant was found by the tenant resolution logic), you can set the `AbpTenantResolveOptions.FallbackTenant` option: + +```csharp +Configure(options => +{ + options.FallbackTenant = "acme"; +}); +``` + +The `FallbackTenant` value can be a tenant name or tenant's Id. This option can be helpful on development time or some specific scenarios to set a constant tenant for the application. It is a simple and consistent way to ensure that a tenant context is always available when needed. However, when you do that, no way to switch to the host side. It is not something you will need it most of the time, but here if you need such a resolution logic. + #### Multi-Tenancy Middleware Multi-Tenancy middleware is an ASP.NET Core request pipeline [middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware) that determines the current tenant from the HTTP request and sets the `ICurrentTenant` properties. diff --git a/docs/en/framework/infrastructure/background-jobs/hangfire.md b/docs/en/framework/infrastructure/background-jobs/hangfire.md index f5991ee19b..8377f001c0 100644 --- a/docs/en/framework/infrastructure/background-jobs/hangfire.md +++ b/docs/en/framework/infrastructure/background-jobs/hangfire.md @@ -159,14 +159,24 @@ app.UseAbpHangfireDashboard("/hangfire", options => `AbpHangfireAuthorizationFilter` class has the following fields: * **`enableTenant` (`bool`, default: `false`):** Enables/disables accessing the Hangfire dashboard on tenant users. -* **`requiredPermissionName` (`string`, default: `null`):** Hangfire dashboard is accessible only if the current user has the specified permission. In this case, if we specify a permission name, we don't need to set `enableTenant` `true` because the permission system already does it. +* **`requiredPermissionName` (`string`, default: `null`):** Hangfire dashboard is accessible only if the current user has the specified permission. +* **`requiredRoleNames` (`string[]`, default: `[]`):** Hangfire dashboard is accessible only if the current user has one of the specified roles. -If you want to require an additional permission, you can pass it into the constructor as below: +If you want to require more policies, you can use the `PolicyBuilder` property of the `AbpHangfireAuthorizationFilter` class. ```csharp app.UseAbpHangfireDashboard("/hangfire", options => { - options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName") }; + var hangfireAuthorizationFilter = new AbpHangfireAuthorizationFilter(requiredPermissionName: "MyHangFireDashboardPermissionName"); + + //hangfireAuthorizationFilter.PolicyBuilder.AddRequirements(new PermissionRequirement("YourPermissionName")); + //hangfireAuthorizationFilter.PolicyBuilder.RequireRole("YourCustomRole"); + //hangfireAuthorizationFilter.PolicyBuilder.Requirements.Add(new YourCustomRequirement()); + + options.AsyncAuthorization = new[] + { + hangfireAuthorizationFilter + }; }); ``` @@ -190,18 +200,20 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = configuration.GetValue("AuthServer:RequireHttpsMetadata"); options.Audience = "MyProjectName"; - }); - context.Services.AddAuthentication() - .AddCookie("Cookies") - .AddOpenIdConnect("oidc", options => + options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase) + ? CookieAuthenticationDefaults.AuthenticationScheme + : null; + }) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) + .AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = configuration.GetValue("AuthServer:RequireHttpsMetadata"); - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); + options.ResponseType = OpenIdConnectResponseType.Code; - options.ClientId = configuration["AuthServer:ClientId"]; - options.ClientSecret = configuration["AuthServer:ClientSecret"]; + options.ClientId = configuration["AuthServer:HangfireClientId"]; + options.ClientSecret = configuration["AuthServer:HangfireClientSecret"]; options.UsePkce = true; options.SaveTokens = true; @@ -211,6 +223,8 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi options.Scope.Add("email"); options.Scope.Add("phone"); options.Scope.Add("MyProjectName"); + + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }); } ``` @@ -218,26 +232,27 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi ```csharp app.Use(async (httpContext, next) => { - if (httpContext.Request.Path.StartsWithSegments("/hangfire")) + if (httpContext.Request.Path.StartsWithSegments("/hangfire", StringComparison.OrdinalIgnoreCase)) { - var result = await httpContext.AuthenticateAsync("Cookies"); - if (result.Succeeded) + var authenticateResult = await httpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + if (!authenticateResult.Succeeded) { - httpContext.User = result.Principal; - await next(httpContext); + await httpContext.ChallengeAsync( + OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties + { + RedirectUri = httpContext.Request.Path + httpContext.Request.QueryString + }); return; } - - await httpContext.ChallengeAsync("oidc"); - } - else - { - await next(httpContext); } + await next.Invoke(); }); - app.UseAbpHangfireDashboard("/hangfire", options => { - options.AsyncAuthorization = new[] {new AbpHangfireAuthorizationFilter()}; + options.AsyncAuthorization = new[] + { + new AbpHangfireAuthorizationFilter() + }; }); ``` diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs index ba21ef11fd..4fc7c4ca75 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Authentication.OAuth; +using Volo.Abp.AspNetCore.Security; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.RemoteServices; @@ -16,5 +17,10 @@ public class AbpAspNetCoreAuthenticationOpenIdConnectModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddHttpClient(); + + Configure(options => + { + options.IgnoredScriptNoncePaths.Add("/signout-oidc"); + }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs index d24e5d6976..870d4a6e5f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Bundling/BlazorWebAssemblyScriptContributor.cs @@ -10,7 +10,6 @@ public class BlazorWebAssemblyScriptContributor : BundleContributor context.Files.AddIfNotContains("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); - context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs index ceb872e5c3..7ccd1a73c3 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Microsoft/AspNetCore/Builder/AbpAspNetCoreMultiTenancyApplicationBuilderExtensions.cs @@ -1,12 +1,34 @@ -using Volo.Abp.AspNetCore.MultiTenancy; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; namespace Microsoft.AspNetCore.Builder; public static class AbpAspNetCoreMultiTenancyApplicationBuilderExtensions { + private const string AuthenticationMiddlewareSetKey = "__AuthenticationMiddlewareSet"; + public static IApplicationBuilder UseMultiTenancy(this IApplicationBuilder app) { - return app - .UseMiddleware(); + var multiTenancyOptions = app.ApplicationServices.GetRequiredService>(); + var hasCurrentUserTenantResolveContributor = multiTenancyOptions.Value.TenantResolvers.Any(r => r is CurrentUserTenantResolveContributor); + if (hasCurrentUserTenantResolveContributor) + { + var authenticationMiddlewareSet = app.Properties.TryGetValue(AuthenticationMiddlewareSetKey, out var value) && value is true; + if (!authenticationMiddlewareSet) + { + var logger = app.ApplicationServices.GetService>(); + logger?.LogWarning( + "MultiTenancyMiddleware is being registered before the authentication middleware. " + + "This may lead to incorrect tenant resolution if the resolution depends on the authenticated user. " + + "Ensure app.UseAuthentication() is called before app.UseMultiTenancy()." + ); + } + } + + return app.UseMiddleware(); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs index 8b46f6e78c..3365ff6a85 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Modal/AbpModalFooterTagHelperService.cs @@ -96,7 +96,7 @@ public class AbpModalFooterTagHelperService : AbpTagHelperService predicate) + { + bundleConfiguration.Contributors.RemoveBundleFile(predicate); + return bundleConfiguration; + } + public static BundleConfiguration AddContributors(this BundleConfiguration bundleConfiguration, params IBundleContributor[] contributors) { Check.NotNull(contributors, nameof(contributors)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs index 5f245c1827..e92538a23f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollection.cs @@ -76,6 +76,36 @@ public class BundleContributorCollection } } + public void RemoveBundleFile(string fileName) + { + RemoveBundleFile([fileName]); + } + + public void RemoveBundleFile(string[] fileNames) + { + var contributors = _contributors + .Where(x => x is BundleFileContributor bundleContributor && + bundleContributor.Files.Any(f => fileNames.Any(name => name.Equals(f.FileName, StringComparison.OrdinalIgnoreCase)))) + .Cast(); + foreach (var contributor in contributors) + { + contributor.Files.RemoveAll(x => fileNames.Any(name => name.Equals(x.FileName, StringComparison.OrdinalIgnoreCase))); + } + } + + public void RemoveBundleFile(Func predicate) + { + var contributors = _contributors + .Where(x => x is BundleFileContributor bundleContributor && + bundleContributor.Files.Any(f => predicate(f.FileName))) + .Cast(); + + foreach (var contributor in contributors) + { + contributor.Files.RemoveAll(x => predicate(x.FileName)); + } + } + public IReadOnlyList GetAll() { return _contributors.ToImmutableList(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs index addbe7e2cd..74f73fa1f3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleContributorCollectionExtensions.cs @@ -1,4 +1,7 @@ -namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling; public static class BundleContributorCollectionExtensions { @@ -16,4 +19,14 @@ public static class BundleContributorCollectionExtensions { contributors.Add(new BundleFileContributor(files)); } + + public static void RemoveFile(this BundleContributorCollection contributors, params string[] files) + { + contributors.RemoveBundleFile(files.ToArray()); + } + + public static void RemoveFile(this BundleContributorCollection contributors, Func predicate) + { + contributors.RemoveBundleFile(predicate); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs index 7dd24fad23..cc5d45dcaa 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs @@ -15,6 +15,10 @@ public class JQueryValidationScriptContributor : BundleContributor public override void ConfigureBundle(BundleConfigurationContext context) { context.Files.AddIfNotContains("/libs/jquery-validation/jquery.validate.js"); + if (context.FileProvider.GetFileInfo("/libs/jquery-validation/abp.jquery.validate.js").Exists) + { + context.Files.AddIfNotContains("/libs/jquery-validation/abp.jquery.validate.js"); + } } public override void ConfigureDynamicResources(BundleConfigurationContext context) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs index 9bc15ae617..d0ba8b98c0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Volo.Abp.Modularity; @@ -15,152 +13,36 @@ public static class ApplicationPartSorter { public static void Sort(ApplicationPartManager partManager, IModuleContainer moduleContainer) { - /* Performing a double Reverse() to preserve the original order for non-sorted parts - */ + var orderedModuleAssemblies = moduleContainer.Modules + .Select((moduleDescriptor, index) => new { moduleDescriptor.Assembly, index }) + .ToDictionary(x => x.Assembly, x => x.index); - var dependencyDictionary = CreateDependencyDictionary(partManager, moduleContainer); + var modulesAssemblies = moduleContainer.Modules.Select(x => x.Assembly).ToList(); + var sortedTypes = partManager.ApplicationParts + .Where(x => modulesAssemblies.Contains(GetApplicationPartAssembly(x))) + .OrderBy(x => orderedModuleAssemblies[GetApplicationPartAssembly(x)]) + .ToList(); - var sortedParts = partManager - .ApplicationParts - .Reverse() //First Revers - .SortByDependencies(p => dependencyDictionary[p]); + var sortIndex = 0; + var sortedParts = partManager.ApplicationParts + .Select(x => modulesAssemblies.Contains(GetApplicationPartAssembly(x)) ? sortedTypes[sortIndex++] : x) + .ToList(); - sortedParts.Reverse(); //Reverse again - - //Replace the original parts with the sorted parts partManager.ApplicationParts.Clear(); + sortedParts.Reverse(); foreach (var applicationPart in sortedParts) { partManager.ApplicationParts.Add(applicationPart); } } - private static Dictionary> CreateDependencyDictionary( - ApplicationPartManager partManager, IModuleContainer moduleContainer) - { - var dependencyDictionary = new Dictionary>(); - - foreach (var applicationPart in partManager.ApplicationParts) - { - dependencyDictionary[applicationPart] = - CreateDependencyList(applicationPart, partManager, moduleContainer); - } - - return dependencyDictionary; - } - - private static List CreateDependencyList( - ApplicationPart applicationPart, - ApplicationPartManager partManager, - IModuleContainer moduleContainer) + private static Assembly GetApplicationPartAssembly(ApplicationPart part) { - var list = new List(); - - if (applicationPart is AssemblyPart assemblyPart) + return part switch { - AddDependencies(list, assemblyPart, partManager, moduleContainer); - } - else if (applicationPart is CompiledRazorAssemblyPart compiledRazorAssemblyPart) - { - AddDependencies(list, compiledRazorAssemblyPart, partManager, moduleContainer); - } - - return list; - } - - private static void AddDependencies( - List list, - AssemblyPart assemblyPart, - ApplicationPartManager partManager, - IModuleContainer moduleContainer) - { - var dependedAssemblyParts = GetDependedAssemblyParts( - partManager, - moduleContainer, - assemblyPart - ); - - list.AddRange(dependedAssemblyParts); - - foreach (var dependedAssemblyPart in dependedAssemblyParts) - { - var viewsPart = GetViewsPartOrNull(partManager, dependedAssemblyPart); - if (viewsPart != null) - { - list.Add(viewsPart); - } - } - } - - private static void AddDependencies( - List list, - CompiledRazorAssemblyPart compiledRazorAssemblyPart, - ApplicationPartManager partManager, - IModuleContainer moduleContainer) - { - if (!compiledRazorAssemblyPart.Name.EndsWith(".Views")) - { - return; - } - - var originalAssemblyPart = GetOriginalAssemblyPartOrNull(compiledRazorAssemblyPart, partManager); - if (originalAssemblyPart == null) - { - return; - } - - list.Add(originalAssemblyPart); - } - - private static AssemblyPart[] GetDependedAssemblyParts( - ApplicationPartManager partManager, - IModuleContainer moduleContainer, - AssemblyPart assemblyPart) - { - var moduleDescriptor = GetModuleDescriptorForAssemblyOrNull(moduleContainer, assemblyPart.Assembly); - if (moduleDescriptor == null) - { - return Array.Empty(); - } - - var moduleDependedAssemblies = moduleDescriptor - .Dependencies - .SelectMany(d => d.AllAssemblies) - .ToArray(); - - return partManager.ApplicationParts - .OfType() - .Where(a => a.Assembly.IsIn(moduleDependedAssemblies)) - .Distinct() - .ToArray(); - } - - private static CompiledRazorAssemblyPart? GetViewsPartOrNull(ApplicationPartManager partManager, - ApplicationPart assemblyPart) - { - var viewsAssemblyName = assemblyPart.Name + ".Views"; - return partManager - .ApplicationParts - .OfType() - .FirstOrDefault(p => p.Name == viewsAssemblyName); - } - - private static AssemblyPart? GetOriginalAssemblyPartOrNull( - CompiledRazorAssemblyPart compiledRazorAssemblyPart, - ApplicationPartManager partManager) - { - var originalAssemblyName = compiledRazorAssemblyPart.Name.RemovePostFix(".Views"); - return partManager.ApplicationParts - .OfType() - .FirstOrDefault(p => p.Name == originalAssemblyName); - } - - private static IAbpModuleDescriptor? GetModuleDescriptorForAssemblyOrNull( - IModuleContainer moduleContainer, - Assembly assembly) - { - return moduleContainer - .Modules - .FirstOrDefault(m => m.AllAssemblies.Contains(assembly)); + AssemblyPart assemblyPart => assemblyPart.Assembly, + CompiledRazorAssemblyPart compiledRazorAssemblyPart => compiledRazorAssemblyPart.Assembly, + _ => throw new AbpException("Unknown application part type") + }; } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs index 2d231c46ee..f1f2c9016e 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs @@ -25,7 +25,9 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende { var endpoint = context.GetEndpoint(); - if (endpoint?.Metadata.GetMetadata() != null) + if (endpoint?.Metadata.GetMetadata() != null || + await AlwaysIgnoreContentTypes(context) || + Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'), StringComparison.OrdinalIgnoreCase))) { await next.Invoke(context); return; @@ -41,13 +43,13 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende AddHeader(context, "X-Frame-Options", "SAMEORIGIN"); var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x => - x!.Contains("text/html") || x.Contains("*/*") || x.Contains("application/xhtml+xml")); + x!.Contains("text/html", StringComparison.OrdinalIgnoreCase) || + x.Contains("*/*", StringComparison.OrdinalIgnoreCase) || + x.Contains("application/xhtml+xml", StringComparison.OrdinalIgnoreCase)); if (!requestAcceptTypeHtml || !Options.Value.UseContentSecurityPolicyHeader - || await AlwaysIgnoreContentTypes(context) - || endpoint == null - || Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'), StringComparison.OrdinalIgnoreCase))) + || endpoint == null) { AddOtherHeaders(context); await next.Invoke(context); @@ -60,7 +62,6 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende context.Items.Add(AbpAspNetCoreConsts.ScriptNonceKey, randomValue); } - context.Response.OnStarting(() => { if (context.Response.Headers.ContainsKey("Content-Security-Policy")) diff --git a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index 8d12eca643..e56ddd83a9 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -4,7 +4,7 @@ - net9.0 + net8.0;net9.0 enable Nullable Volo.Abp.AutoMapper diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs index 12e24c8f81..f58337e400 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Volo.Abp.Modularity; using Volo.Abp.Threading; @@ -16,9 +18,11 @@ public class AbpBackgroundWorkersModule : AbpModule var options = context.ServiceProvider.GetRequiredService>().Value; if (options.IsEnabled) { + var hostApplicationLifetime = context.ServiceProvider.GetService(); + var cancellationToken = hostApplicationLifetime?.ApplicationStopping ?? CancellationToken.None; await context.ServiceProvider .GetRequiredService() - .StartAsync(); + .StartAsync(cancellationToken); } } @@ -27,9 +31,11 @@ public class AbpBackgroundWorkersModule : AbpModule var options = context.ServiceProvider.GetRequiredService>().Value; if (options.IsEnabled) { + var hostApplicationLifetime = context.ServiceProvider.GetService(); + var cancellationToken = hostApplicationLifetime?.ApplicationStopping ?? CancellationToken.None; await context.ServiceProvider .GetRequiredService() - .StopAsync(); + .StopAsync(cancellationToken); } } diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs index 652aa35741..4f7a65226d 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace Volo.Abp.BackgroundWorkers; @@ -28,6 +29,15 @@ public static class BackgroundWorkersApplicationInitializationContextExtensions throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!"); } + if (cancellationToken == default) + { + var hostApplicationLifetime = context.ServiceProvider.GetService(); + if (hostApplicationLifetime != null) + { + cancellationToken = hostApplicationLifetime.ApplicationStopping; + } + } + await context.ServiceProvider .GetRequiredService() .AddAsync((IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType), cancellationToken); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs index 71964653a7..092e996095 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Cli.Commands; @@ -13,11 +14,14 @@ namespace Volo.Abp.Cli.Commands; public class CleanCommand : IConsoleCommand, ITransientDependency { public const string Name = "clean"; - + public ILogger Logger { get; set; } - public CleanCommand() + protected ICmdHelper CmdHelper { get; } + + public CleanCommand(ICmdHelper cmdHelper) { + CmdHelper = cmdHelper; Logger = NullLogger.Instance; } @@ -26,6 +30,10 @@ public class CleanCommand : IConsoleCommand, ITransientDependency var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories); var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories); + Logger.LogInformation("Cleaning the solution with 'dotnet clean' command..."); + CmdHelper.RunCmd($"dotnet clean", workingDirectory: Directory.GetCurrentDirectory()); + + Logger.LogInformation($"Removing 'bin' and 'obj' folders..."); foreach (var path in binEntries.Concat(objEntries)) { if (path.IndexOf("node_modules", StringComparison.OrdinalIgnoreCase) > 0) @@ -38,9 +46,9 @@ public class CleanCommand : IConsoleCommand, ITransientDependency Directory.Delete(path, true); } } + Logger.LogInformation($"'bin' and 'obj' folders removed successfully!"); - Logger.LogInformation($"BIN and OBJ folders have been successfully deleted!"); - + Logger.LogInformation("Solution cleaned successfully!"); return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs b/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs index bd65b22dba..d49c211ee5 100644 --- a/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs +++ b/framework/src/Volo.Abp.HangFire/Volo/Abp/Hangfire/AbpHangfireAuthorizationFilter.cs @@ -1,53 +1,49 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Hangfire.Dashboard; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Users; +using Volo.Abp.Authorization; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Hangfire; public class AbpHangfireAuthorizationFilter : IDashboardAsyncAuthorizationFilter { private readonly bool _enableTenant; - private readonly string? _requiredPermissionName; + private readonly AuthorizationPolicyBuilder _policyBuilder; - public AbpHangfireAuthorizationFilter(bool enableTenant = false, string? requiredPermissionName = null) - { - _enableTenant = requiredPermissionName.IsNullOrWhiteSpace() ? enableTenant : true; - _requiredPermissionName = requiredPermissionName; - } + public virtual AuthorizationPolicyBuilder PolicyBuilder => _policyBuilder; - public async Task AuthorizeAsync(DashboardContext context) + public AbpHangfireAuthorizationFilter(bool enableTenant = false, string? requiredPermissionName = null, params string[]? requiredRoleNames) { - if (!IsLoggedIn(context, _enableTenant)) + _enableTenant = enableTenant; + _policyBuilder = new AuthorizationPolicyBuilder().RequireAuthenticatedUser(); + if (!requiredPermissionName.IsNullOrWhiteSpace()) { - return false; + _policyBuilder.Requirements.Add(new PermissionRequirement(requiredPermissionName)); } - if (_requiredPermissionName.IsNullOrEmpty()) + if (!requiredRoleNames.IsNullOrEmpty()) { - return true; + foreach (var roleName in requiredRoleNames!) + { + _policyBuilder.RequireRole(roleName); + } } - - return await IsPermissionGrantedAsync(context, _requiredPermissionName!); } - private static bool IsLoggedIn(DashboardContext context, bool enableTenant) + public virtual async Task AuthorizeAsync(DashboardContext context) { - var currentUser = context.GetHttpContext().RequestServices.GetRequiredService(); - - if (!enableTenant) + var currentTenant = context.GetHttpContext().RequestServices.GetRequiredService(); + if (currentTenant.IsAvailable && !_enableTenant) { - return currentUser.IsAuthenticated && !currentUser.TenantId.HasValue; + return false; } - return currentUser.IsAuthenticated; - } - - private static async Task IsPermissionGrantedAsync(DashboardContext context, string requiredPermissionName) - { - var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService(); - return await permissionChecker.IsGrantedAsync(requiredPermissionName); + var authorizationService = context.GetHttpContext().RequestServices.GetRequiredService(); + var authorizationPolicy = _policyBuilder.Build(); + return (await authorizationService.AuthorizeAsync(context.GetHttpContext().User, authorizationPolicy)).Succeeded; } } diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs index 414983c87a..26640c3dfd 100644 --- a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs @@ -8,6 +8,11 @@ public class AbpTenantResolveOptions [NotNull] public List TenantResolvers { get; } + /// + /// Fallback tenant to use when no other resolver resolves a tenant. + /// + public string? FallbackTenant { get; set; } + public AbpTenantResolveOptions() { TenantResolvers = new List(); diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverNames.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverNames.cs new file mode 100644 index 0000000000..3542d3a948 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverNames.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.MultiTenancy; + +public static class TenantResolverNames +{ + public const string FallbackTenant = "FallbackTenant"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs index 3ae9ae67b1..4351ef62bb 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs @@ -39,6 +39,12 @@ public class TenantResolver : ITenantResolver, ITransientDependency } } + if (result.TenantIdOrName.IsNullOrEmpty() && !string.IsNullOrWhiteSpace(_options.FallbackTenant)) + { + result.TenantIdOrName = _options.FallbackTenant; + result.AppliedResolvers.Add(TenantResolverNames.FallbackTenant); + } + return result; } } diff --git a/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs b/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs index 7776b34b13..1f53e36260 100644 --- a/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo/Abp/AspNetCore/MultiTenancy/AspNetCoreMultiTenancy_Without_DomainResolver_Tests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs new file mode 100644 index 0000000000..829aa23527 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using NSubstitute; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationPart; + +public class ApplicationPartSorter_Tests +{ + [Fact] + public void Should_Sort_ApplicationParts_By_Module_Dependencies() + { + var moduleDescriptors = new List(); + var partManager = new ApplicationPartManager(); + + for (var i = 0; i < 10; i++) + { + var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"ModuleA{i}.dll"), AssemblyBuilderAccess.Run); + partManager.ApplicationParts.Add(new AssemblyPart(assembly)); + moduleDescriptors.Add(CreateModuleDescriptor(assembly)); + } + var randomApplicationParts = partManager.ApplicationParts.OrderBy(x => Guid.NewGuid()).ToList(); // Shuffle the parts + + // Additional part + randomApplicationParts.AddFirst(new CompiledRazorAssemblyPart(typeof(AbpAspNetCoreModule).Assembly)); + randomApplicationParts.Insert(5, new CompiledRazorAssemblyPart(typeof(AbpAspNetCoreMvcModule).Assembly)); + randomApplicationParts.AddLast(new CompiledRazorAssemblyPart(typeof(AbpVirtualFileSystemModule).Assembly)); + + partManager.ApplicationParts.Clear(); + foreach (var part in randomApplicationParts) + { + partManager.ApplicationParts.Add(part); + } + + var moduleContainer = CreateFakeModuleContainer(moduleDescriptors); + + ApplicationPartSorter.Sort(partManager, moduleContainer); + + // Act + partManager.ApplicationParts.Count.ShouldBe(13); // 10 modules + 3 additional parts + + var applicationParts = partManager.ApplicationParts.Reverse().ToList(); // Reverse the order to match the expected output + + applicationParts[0].ShouldBeOfType().Assembly.ShouldBe(typeof(AbpAspNetCoreModule).Assembly); + applicationParts[1].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA0"); + applicationParts[2].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA1"); + applicationParts[3].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA2"); + applicationParts[4].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA3"); + applicationParts[5].ShouldBeOfType().Assembly.ShouldBe(typeof(AbpAspNetCoreMvcModule).Assembly); + applicationParts[6].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA4"); + applicationParts[7].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA5"); + applicationParts[8].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA6"); + applicationParts[9].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA7"); + applicationParts[10].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA8"); + applicationParts[11].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA9"); + applicationParts[12].ShouldBeOfType().Assembly.ShouldBe(typeof(AbpVirtualFileSystemModule).Assembly); + } + + private static IModuleContainer CreateFakeModuleContainer(List moduleDescriptors) + { + var fakeModuleContainer = Substitute.For(); + fakeModuleContainer.Modules.Returns(moduleDescriptors); + return fakeModuleContainer; + } + + private static IAbpModuleDescriptor CreateModuleDescriptor(Assembly assembly) + { + var moduleDescriptor = Substitute.For(); + moduleDescriptor.Assembly.Returns(assembly); + return moduleDescriptor; + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs index 8e92558cb8..aa6462996a 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs @@ -2,10 +2,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Security.Headers; +[Route("SecurityHeadersTest")] public class SecurityHeadersTestController : AbpController { + [HttpGet("Get")] public ActionResult Get() { return Content("OK"); } + + [HttpGet("ignored")] + public ActionResult Get_Ignored() + { + return Content("OK"); + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs index 336c7fe8db..b71103620b 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs @@ -15,6 +15,8 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase { options.UseContentSecurityPolicyHeader = true; options.Headers["Referrer-Policy"] = "no-referrer"; + + options.IgnoredScriptNoncePaths.Add("/SecurityHeadersTest/ignored"); }); base.ConfigureServices(services); @@ -27,7 +29,6 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff"); responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block"); responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN"); - responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff"); } [Fact] @@ -37,4 +38,15 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase responseMessage.Headers.ShouldNotBeEmpty(); responseMessage.Headers.ShouldContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer"); } + + [Fact] + public async Task SecurityHeaders_Should_Be_Ignored() + { + var responseMessage = await GetResponseAsync("/SecurityHeadersTest/ignored"); + responseMessage.Headers.ShouldNotBeEmpty(); + responseMessage.Headers.ShouldNotContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff"); + responseMessage.Headers.ShouldNotContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block"); + responseMessage.Headers.ShouldNotContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN"); + responseMessage.Headers.ShouldNotContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer"); + } } diff --git a/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/FallbackTenantResolveContributor_Tests.cs b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/FallbackTenantResolveContributor_Tests.cs new file mode 100644 index 0000000000..00bf669aaf --- /dev/null +++ b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/FallbackTenantResolveContributor_Tests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.MultiTenancy.ConfigurationStore; +using Xunit; + +namespace Volo.Abp.MultiTenancy; + +public class FallbackTenantResolveContributor_Tests : MultiTenancyTestBase +{ + private readonly Guid _testTenantId = Guid.NewGuid(); + private readonly string _testTenantName = "acme"; + private readonly string _testTenantNormalizedName = "ACME"; + + private readonly AbpTenantResolveOptions _options; + private readonly ITenantResolver _tenantResolver; + + public FallbackTenantResolveContributor_Tests() + { + _options = ServiceProvider.GetRequiredService>().Value; + _tenantResolver = ServiceProvider.GetRequiredService(); + } + + protected override void BeforeAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.Tenants = new[] + { + new TenantConfiguration(_testTenantId, _testTenantName, _testTenantNormalizedName) + }; + }); + + services.Configure(options => + { + options.FallbackTenant = _testTenantName; + }); + } + + [Fact] + public async Task Should_Resolve_To_Fallback_Tenant_If_No_Other_Contributor_Succeeds() + { + var result = await _tenantResolver.ResolveTenantIdOrNameAsync(); + + result.TenantIdOrName.ShouldBe(_testTenantName); + result.AppliedResolvers.ShouldContain(TenantResolverNames.FallbackTenant); + } + + [Fact] + public async Task Should_Not_Override_Resolved_Tenant() + { + // Arrange + var customTenantName = "resolved-tenant"; + _options.TenantResolvers.Insert(0, new TestTenantResolveContributor(customTenantName)); + + // Act + var result = await _tenantResolver.ResolveTenantIdOrNameAsync(); + + // Assert + result.TenantIdOrName.ShouldBe(customTenantName); + result.AppliedResolvers.First().ShouldBe("Test"); + result.AppliedResolvers.ShouldNotContain(TenantResolverNames.FallbackTenant); + } + + public class TestTenantResolveContributor : TenantResolveContributorBase + { + private readonly string _tenant; + + public TestTenantResolveContributor(string tenant) + { + _tenant = tenant; + } + + public override string Name => "Test"; + + public override Task ResolveAsync(ITenantResolveContext context) + { + context.TenantIdOrName = _tenant; + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/latest-versions.json b/latest-versions.json index 7e756d726b..1d49e265f6 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,22 @@ [ + { + "version": "9.2.2", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.2.2" + } + }, + { + "version": "9.2.1", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.2.1" + } + }, { "version": "9.2.0", "releaseDate": "", diff --git a/modules/README.md b/modules/README.md index 670d60c1cc..4eea191640 100644 --- a/modules/README.md +++ b/modules/README.md @@ -1,3 +1,5 @@ ## ABP Free Modules -These modules are free & open source. \ No newline at end of file +These modules are free & open source. + +For detailed information about module architecture and installer projects, see the [Module Installer Projects documentation](../docs/en/framework/architecture/modularity/installer-projects.md). \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css index 36ca24e100..71840b79d2 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css @@ -10,7 +10,7 @@ body a { text-decoration: none; } body .btn-primary { - background-color: #b84297 !important; + background-color: #E83090 !important; font-size: 12px; } body .for-mobile { @@ -298,7 +298,7 @@ body.scrolledMore .alert-criteria p.alert-p { } .docs-page .docs-sidebar .docs-tree-list ul li.selected-tree > span .fa { transform: rotate(90deg); - color: #b84297; + color: #E83090; } .docs-page .docs-sidebar .docs-tree-list ul li.selected-tree.last-link > span .fa { transform: rotate(0deg); @@ -482,7 +482,7 @@ body.scrolledMore .alert-criteria p.alert-p { margin-bottom: 1rem; margin-left: 0; padding: 1em 1.5em; - background-color: #e3edf2; + background-color: rgb(227, 237, 242); font-size: 1em; border-radius: 12px; color: #385766; @@ -549,7 +549,7 @@ body.scrolledMore .alert-criteria p.alert-p { background-color: #f4f6fa; border-color: #f4f6fa; border-radius: 12px; - background: rgba(190, 223, 238, 0.82); + background: hsla(199, 59%, 84%, 0.82); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); z-index: 3; @@ -600,16 +600,16 @@ body.scrolledMore .alert-criteria p.alert-p { font-weight: normal; } .docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-link.active { - border-left: 1px solid #b84297; + border-left: 1px solid #E83090; background: none; - color: #b84297; + color: #E83090; font-weight: normal; } .docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-pills .nav-link.active { - color: #b84297; + color: #E83090; } .docs-page .docs-page-index .docs-inner-anchors .navbar .nav-pills .nav-pills .nav-pills .nav-link.active { - color: #b84297; + color: #E83090; } .docs-page .docs-page-index .docs-inner-anchors .index-scroll { margin-left: -30px; @@ -667,7 +667,7 @@ body.scrolledMore .alert-criteria p.alert-p { display: none; } body .close-mmenu, -body .close-dmenu { + body .close-dmenu { position: absolute; top: -78px; left: 25px; @@ -720,7 +720,7 @@ body .close-dmenu { display: none; } .docs-page .docs-sidebar .docs-top .navbar.navbar-logo .navbar-collapse { - background: #38003d; + background: rgb(56, 0, 61); position: fixed; top: 86px; left: 0; diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss index deba3d39c3..9d5de59393 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss @@ -16,7 +16,7 @@ body { } .btn-primary { - background-color: #b84297 !important; + background-color: #E83090 !important; font-size: 12px; } @@ -368,7 +368,7 @@ body { > span { .fa { transform: rotate(90deg); - color: #b84297; + color: #E83090; } } @@ -734,9 +734,9 @@ body { font-weight: normal; &.active { - border-left: 1px solid #b84297; + border-left: 1px solid #E83090; background: none; - color: #b84297; + color: #E83090; font-weight: normal; } } @@ -744,14 +744,14 @@ body { .nav-pills { .nav-link { &.active { - color: #b84297; + color: #E83090; } } .nav-pills { .nav-link { &.active { - color: #b84297; + color: #E83090; } } } diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs b/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs index 2e55cc24b1..87948a4cdc 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs @@ -75,6 +75,41 @@ Console.WriteLine("UserInfo: {0}", JsonSerializer.Serialize(JsonDocument.Parse(u })); Console.WriteLine(); +var tokenExchangeResponse = await client.RequestTokenExchangeTokenAsync(new TokenExchangeTokenRequest() +{ + Address = configuration.TokenEndpoint, + ClientId = clientId, + ClientSecret = clientSecret, + SubjectToken = refreshTokenResponse.AccessToken!, + SubjectTokenType = "urn:ietf:params:oauth:token-type:access_token", + Scope = "AbpAPI profile roles email phone offline_access", +}); + +if (tokenExchangeResponse.IsError) +{ + throw new Exception(tokenExchangeResponse.Error); +} + +Console.WriteLine("Token Exchange token: {0}", tokenExchangeResponse.AccessToken); +Console.WriteLine(); +Console.WriteLine("Token Exchange token: {0}", tokenExchangeResponse.RefreshToken); +Console.WriteLine(); + +userinfo = await client.GetUserInfoAsync(new UserInfoRequest() +{ + Address = configuration.UserInfoEndpoint, + Token = tokenExchangeResponse.AccessToken +}); +if (userinfo.IsError) +{ + throw new Exception(userinfo.Error); +} + +Console.WriteLine("Token Exchange UserInfo: {0}", JsonSerializer.Serialize(JsonDocument.Parse(userinfo.Raw), new JsonSerializerOptions +{ + WriteIndented = true +})); +Console.WriteLine(); var introspectionResponse = await client.IntrospectTokenAsync(new TokenIntrospectionRequest() { diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml b/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml index dc32cca59c..ea4a9f3347 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml @@ -15,17 +15,38 @@ @if (HttpContext.User.Identity != null && HttpContext.User.Identity.IsAuthenticated) {
    +

    Current User

    @foreach (var claim in HttpContext.User.Claims) {
  • @claim.Type : @claim.Value
  • }
+
    +

    oidc

    + @{ + var oidc = await HttpContext.AuthenticateAsync("oidc"); + if (oidc.Principal != null) + { + foreach (var claim in oidc.Principal.Claims) + { +
  • @claim.Type : @claim.Value
  • + } + } + } +
+ +

HttpContext.GetTokenAsync("access_token")
@await HttpContext.GetTokenAsync("access_token")

+

HttpContext.GetTokenAsync("id_token") +
+ @await HttpContext.GetTokenAsync("id_token") +

+ var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44303/api/claims"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await HttpContext.GetTokenAsync("access_token")); diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs b/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs index c67b2977ef..e1fd97136b 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs @@ -79,6 +79,7 @@ public class ServerDataSeedContributor : IDataSeedContributor, ITransientDepende OpenIddictConstants.Permissions.GrantTypes.RefreshToken, OpenIddictConstants.Permissions.GrantTypes.DeviceCode, OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, + OpenIddictConstants.Permissions.GrantTypes.TokenExchange, OpenIddictConstants.Permissions.Prefixes.GrantType + MyTokenExtensionGrant.ExtensionGrantName, OpenIddictConstants.Permissions.ResponseTypes.Code, diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.Designer.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.Designer.cs similarity index 99% rename from modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.Designer.cs rename to modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.Designer.cs index fec1a9f143..1ba6189674 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.Designer.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace OpenIddict.Demo.Server.Migrations { [DbContext(typeof(ServerDbContext))] - [Migration("20250215074649_Initial")] + [Migration("20250710090114_Initial")] partial class Initial { /// @@ -22,7 +22,7 @@ namespace OpenIddict.Demo.Server.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("ProductVersion", "9.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -938,6 +938,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1161,8 +1164,8 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.cs similarity index 99% rename from modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.cs rename to modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.cs index fdf8dac987..a992bf78c2 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250215074649_Initial.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20250710090114_Initial.cs @@ -378,6 +378,7 @@ namespace OpenIddict.Demo.Server.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -644,7 +645,7 @@ namespace OpenIddict.Demo.Server.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs index 4099fccc43..b1caafb242 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs @@ -19,7 +19,7 @@ namespace OpenIddict.Demo.Server.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("ProductVersion", "9.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -935,6 +935,9 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1158,8 +1161,8 @@ namespace OpenIddict.Demo.Server.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Pages/Index.cshtml b/modules/openiddict/app/OpenIddict.Demo.Server/Pages/Index.cshtml index fd28464cc7..daa3ee9b95 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/Pages/Index.cshtml +++ b/modules/openiddict/app/OpenIddict.Demo.Server/Pages/Index.cshtml @@ -4,7 +4,13 @@ ViewData["Title"] = "Home page"; } -
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

-
\ No newline at end of file +@if (HttpContext.User.Identity != null && HttpContext.User.Identity.IsAuthenticated) +{ +
    +

    Current User

    + @foreach (var claim in HttpContext.User.Claims) + { +
  • @claim.Type : @claim.Value
  • + } +
+} diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index dddc89d515..3a9c8109fc 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -79,7 +79,8 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule .AllowClientCredentialsFlow() .AllowRefreshTokenFlow() .AllowDeviceAuthorizationFlow() - .AllowNoneFlow(); + .AllowNoneFlow() + .AllowTokenExchangeFlow(); builder.RegisterScopes(new[] { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictErrors.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictErrors.cs new file mode 100644 index 0000000000..45422ad210 --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictErrors.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.OpenIddict; + +public static class AbpOpenIddictErrors +{ + public const string AccountLocked = "account_locked"; + + public const string AccountInactive = "account_inactive"; +} diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs index 5c2f6ef996..6216571168 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; @@ -43,7 +44,7 @@ public class AuthorizeController : AbpOpenIdDictControllerBase var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); if (result is not { Succeeded: true } || ((request.HasPromptValue(OpenIddictConstants.PromptValues.Login) || request.MaxAge is 0 || - (request.MaxAge != null && result.Properties?.IssuedUtc != null && + (request.MaxAge is not null && result.Properties?.IssuedUtc is not null && TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) && TempData["IgnoreAuthenticationChallenge"] is null or false)) { @@ -148,6 +149,13 @@ public class AuthorizeController : AbpOpenIdDictControllerBase case OpenIddictConstants.ConsentTypes.Explicit when authorizations.Any() && !request.HasPromptValue(OpenIddictConstants.PromptValues.Consent): var principal = await SignInManager.CreateUserPrincipalAsync(user); + var sid = dynamicPrincipal.FindFirst(JwtRegisteredClaimNames.Sid); + if (sid != null) + { + principal.RemoveClaims(JwtRegisteredClaimNames.Sid); + principal.AddClaim(JwtRegisteredClaimNames.Sid, sid.Value); + } + if (result.Properties != null && result.Properties.IsPersistent) { var claim = new Claim(AbpClaimTypes.RememberMe, true.ToString()).SetDestinations(OpenIddictConstants.Destinations.AccessToken); @@ -247,6 +255,13 @@ public class AuthorizeController : AbpOpenIdDictControllerBase var principal = await SignInManager.CreateUserPrincipalAsync(user); + var sid = User.FindFirst(JwtRegisteredClaimNames.Sid); + if (sid != null) + { + principal.RemoveClaims(JwtRegisteredClaimNames.Sid); + principal.AddClaim(JwtRegisteredClaimNames.Sid, sid.Value); + } + var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); if (result.Succeeded && result.Properties != null && result.Properties.IsPersistent) { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs index 33a1a37845..5059c7c5ca 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs @@ -107,10 +107,13 @@ public partial class TokenController ClientId = request.ClientId }); + var errorCode = OpenIddictConstants.Errors.InvalidGrant; string errorDescription; + if (result.IsLockedOut) { Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", request.Username); + errorCode = AbpOpenIddictErrors.AccountLocked; errorDescription = "The user account has been locked out due to invalid login attempts. Please wait a while and try again."; } else if (result.IsNotAllowed) @@ -139,7 +142,8 @@ public partial class TokenController return await HandleConfirmUserAsync(request, user); } - errorDescription = "You are not allowed to login! Your account is inactive."; + errorCode = AbpOpenIddictErrors.AccountInactive; + errorDescription = "You are not allowed to login! Your account is inactive or needs to confirm your email/phone number."; } } else @@ -150,7 +154,7 @@ public partial class TokenController var properties = new AuthenticationProperties(new Dictionary { - [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.Error] = errorCode, [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = errorDescription }); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs new file mode 100644 index 0000000000..8f17a34be0 --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using OpenIddict.Abstractions; +using OpenIddict.Server.AspNetCore; + +namespace Volo.Abp.OpenIddict.Controllers; + +public partial class TokenController +{ + protected virtual async Task HandleTokenExchangeGrantTypeAsync(OpenIddictRequest request) + { + // Retrieve the claims principal stored in the subject token. + // + // Note: the principal may not represent a user (e.g if the token was issued during a client credentials token + // request and represents a client application): developers are strongly encouraged to ensure that the user + // and client identifiers are randomly generated so that a malicious client cannot impersonate a legit user. + // + // See https://datatracker.ietf.org/doc/html/rfc9068#SecurityConsiderations for more information. + var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + + // If available, retrieve the claims principal stored in the actor token. + var actor = result.Properties?.GetParameter(OpenIddictServerAspNetCoreConstants.Properties.ActorTokenPrincipal); + + // Retrieve the user profile corresponding to the subject token. + var user = await UserManager.FindByIdAsync(result.Principal!.GetClaim(OpenIddictConstants.Claims.Subject)!); + if (user is null) + { + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid." + })); + } + + // Ensure the user is still allowed to sign in. + if (!await PreSignInCheckAsync(user)) + { + return Forbid( + authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, + properties: new AuthenticationProperties(new Dictionary + { + [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, + [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." + })); + } + + // Note: whether the identity represents a delegated or impersonated access (or any other + // model) is entirely up to the implementer: to support all scenarios, OpenIddict doesn't + // enforce any specific constraint on the identity used for the sign-in operation and only + // requires that the standard "act" and "may_act" claims be valid JSON objects if present. + + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + + // Create a new ClaimsPrincipal containing the claims that + // will be used to create an id_token, a token or a code. + var principal = await SignInManager.CreateUserPrincipalAsync(user); + + // Note: IdentityModel doesn't support serializing ClaimsIdentity.Actor to the + // standard "act" claim yet, which requires adding the "act" claim manually. + // + // For more information, see + // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3219. + if (!string.IsNullOrEmpty(actor?.GetClaim(OpenIddictConstants.Claims.Subject)) && + !string.Equals(principal.GetClaim(OpenIddictConstants.Claims.Subject), actor.GetClaim(OpenIddictConstants.Claims.Subject), StringComparison.Ordinal)) + { + principal.SetClaim(OpenIddictConstants.Claims.Actor, new JsonObject + { + [OpenIddictConstants.Claims.Subject] = actor.GetClaim(OpenIddictConstants.Claims.Subject) + }); + } + + // Note: in this sample, the granted scopes match the requested scope + // but you may want to allow the user to uncheck specific scopes. + // For that, simply restrict the list of scopes before calling SetScopes. + principal.SetScopes(request.GetScopes()); + principal.SetResources(await GetResourcesAsync(request.GetScopes())); + + await OpenIddictClaimsPrincipalManager.HandleAsync(request, principal); + + // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens. + return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); + } +} diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs index e3b3d10c39..e348ffe007 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs @@ -42,6 +42,11 @@ public partial class TokenController : AbpOpenIdDictControllerBase return await HandleClientCredentialsAsync(request); } + if (request.IsTokenExchangeGrantType()) + { + return await HandleTokenExchangeGrantTypeAsync(request); + } + var extensionGrantsOptions = HttpContext.RequestServices.GetRequiredService>(); var extensionTokenGrant = extensionGrantsOptions.Value.Find(request.GrantType); if (extensionTokenGrant != null) diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Tokens/OpenIddictTokenConsts.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Tokens/OpenIddictTokenConsts.cs index c0e2674911..847cf0ef7a 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Tokens/OpenIddictTokenConsts.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo/Abp/OpenIddict/Tokens/OpenIddictTokenConsts.cs @@ -8,5 +8,5 @@ public class OpenIddictTokenConsts public static int SubjectMaxLength { get; set; } = 400; - public static int TypeMaxLength { get; set; } = 50; + public static int TypeMaxLength { get; set; } = 150; } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictDomainModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictDomainModule.cs index 1312ad5d8c..fd20f71012 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictDomainModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/AbpOpenIddictDomainModule.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; +using OpenIddict.Core; using Volo.Abp.BackgroundWorkers; using Volo.Abp.Caching; using Volo.Abp.DistributedLocking; @@ -65,15 +66,15 @@ public class AbpOpenIddictDomainModule : AbpModule .SetDefaultTokenEntity(); builder - .AddApplicationStore() - .AddAuthorizationStore() - .AddScopeStore() - .AddTokenStore(); - - builder.ReplaceApplicationManager(typeof(AbpApplicationManager)); - builder.ReplaceAuthorizationManager(typeof(AbpAuthorizationManager)); - builder.ReplaceScopeManager(typeof(AbpScopeManager)); - builder.ReplaceTokenManager(typeof(AbpTokenManager)); + .ReplaceApplicationStore() + .ReplaceAuthorizationStore() + .ReplaceScopeStore() + .ReplaceTokenStore(); + + builder.ReplaceApplicationManager(); + builder.ReplaceAuthorizationManager(); + builder.ReplaceScopeManager(); + builder.ReplaceTokenManager(); builder.Services.TryAddScoped(provider => (IAbpApplicationManager)provider.GetRequiredService()); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationDescriptor.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationDescriptor.cs index 3b5a4fc247..5fb2de1205 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationDescriptor.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationDescriptor.cs @@ -1,16 +1,22 @@ -using OpenIddict.Abstractions; +using System; +using OpenIddict.Abstractions; namespace Volo.Abp.OpenIddict.Applications; public class AbpApplicationDescriptor : OpenIddictApplicationDescriptor { + /// + /// Gets or sets the front-channel logout URI associated with the application. + /// + public virtual Uri FrontChannelLogoutUri { get; set; } + /// /// URI to further information about client. /// - public string ClientUri { get; set; } + public virtual string ClientUri { get; set; } /// /// URI to client logo. /// - public string LogoUri { get; set; } + public virtual string LogoUri { get; set; } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs index e02d671652..ea2283ea25 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpApplicationManager.cs @@ -17,7 +17,7 @@ public class AbpApplicationManager : OpenIddictApplicationManager cache, [NotNull] ILogger logger, [NotNull] IOptionsMonitor options, - [NotNull] IOpenIddictApplicationStoreResolver resolver, + [NotNull] IOpenIddictApplicationStore resolver, AbpOpenIddictIdentifierConverter identifierConverter) : base(cache, logger, options, resolver) { @@ -44,6 +44,17 @@ public class AbpApplicationManager : OpenIddictApplicationManager GetFrontChannelLogoutUriAsync(object application, CancellationToken cancellationToken = default) + { + Check.NotNull(application, nameof(application)); + Check.AssignableTo(application.GetType(), nameof(application)); + + return await Store.As().GetFrontChannelLogoutUriAsync(application.As(), cancellationToken); + } + public virtual async ValueTask GetClientUriAsync(object application, CancellationToken cancellationToken = default) { Check.NotNull(application, nameof(application)); - Check.AssignableTo(application.GetType(), nameof(application)); + Check.AssignableTo(application.GetType(), nameof(application)); return await Store.As().GetClientUriAsync(application.As(), cancellationToken); } @@ -71,8 +91,15 @@ public class AbpApplicationManager : OpenIddictApplicationManager GetLogoUriAsync(object application, CancellationToken cancellationToken = default) { Check.NotNull(application, nameof(application)); - Check.AssignableTo(application.GetType(), nameof(application)); + Check.AssignableTo(application.GetType(), nameof(application)); return await Store.As().GetLogoUriAsync(application.As(), cancellationToken); } + + protected virtual bool IsImplicitFileUri(Uri uri) + { + Check.NotNull(uri, nameof(uri)); + + return uri.IsAbsoluteUri && uri.IsFile && !uri.OriginalString.StartsWith(uri.Scheme, StringComparison.OrdinalIgnoreCase); + } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpOpenIddictApplicationStore.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpOpenIddictApplicationStore.cs index 416d90b3b7..f604a0960c 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpOpenIddictApplicationStore.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/AbpOpenIddictApplicationStore.cs @@ -635,6 +635,13 @@ public class AbpOpenIddictApplicationStore : AbpOpenIddictStoreBase GetFrontChannelLogoutUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default) + { + Check.NotNull(application, nameof(application)); + + return await new ValueTask(application.FrontChannelLogoutUri); + } + public virtual ValueTask GetClientUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default) { Check.NotNull(application, nameof(application)); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpApplicationManager.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpApplicationManager.cs index 1f12a9d088..b0dd375908 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpApplicationManager.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpApplicationManager.cs @@ -6,6 +6,8 @@ namespace Volo.Abp.OpenIddict.Applications; public interface IAbpApplicationManager : IOpenIddictApplicationManager { + ValueTask GetFrontChannelLogoutUriAsync(object application, CancellationToken cancellationToken = default); + ValueTask GetClientUriAsync(object application, CancellationToken cancellationToken = default); ValueTask GetLogoUriAsync(object application, CancellationToken cancellationToken = default); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpOpenIdApplicationStore.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpOpenIdApplicationStore.cs index ca2cd50102..9dd0b70515 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpOpenIdApplicationStore.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/IAbpOpenIdApplicationStore.cs @@ -6,6 +6,8 @@ namespace Volo.Abp.OpenIddict.Applications; public interface IAbpOpenIdApplicationStore : IOpenIddictApplicationStore { + ValueTask GetFrontChannelLogoutUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default); + ValueTask GetClientUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default); ValueTask GetLogoUriAsync(OpenIddictApplicationModel application, CancellationToken cancellationToken = default); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplication.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplication.cs index e88370e874..d2f7b5f752 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplication.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplication.cs @@ -94,13 +94,18 @@ public class OpenIddictApplication : FullAuditedAggregateRoot /// public virtual string Settings { get; set; } + /// + /// Gets or sets the front-channel logout URI associated with the application. + /// + public virtual string FrontChannelLogoutUri { get; set; } + /// /// URI to further information about client. /// - public string ClientUri { get; set; } + public virtual string ClientUri { get; set; } /// /// URI to client logo. /// - public string LogoUri { get; set; } + public virtual string LogoUri { get; set; } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationExtensions.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationExtensions.cs index 791136316a..818a40f973 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationExtensions.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationExtensions.cs @@ -27,6 +27,7 @@ public static class OpenIddictApplicationExtensions RedirectUris = model.RedirectUris, Requirements = model.Requirements, Settings = model.Settings, + FrontChannelLogoutUri = model.FrontChannelLogoutUri, ClientUri = model.ClientUri, LogoUri = model.LogoUri }; @@ -59,6 +60,7 @@ public static class OpenIddictApplicationExtensions entity.RedirectUris = model.RedirectUris; entity.Requirements = model.Requirements; entity.Settings = model.Settings; + entity.FrontChannelLogoutUri = model.FrontChannelLogoutUri; entity.ClientUri = model.ClientUri; entity.LogoUri = model.LogoUri; @@ -100,6 +102,7 @@ public static class OpenIddictApplicationExtensions RedirectUris = entity.RedirectUris, Requirements = entity.Requirements, Settings = entity.Settings, + FrontChannelLogoutUri = entity.FrontChannelLogoutUri, ClientUri = entity.ClientUri, LogoUri = entity.LogoUri }; diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationModel.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationModel.cs index 48a376769f..6841b10a04 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationModel.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Applications/OpenIddictApplicationModel.cs @@ -89,13 +89,18 @@ public class OpenIddictApplicationModel : ExtensibleObject /// public virtual string Settings { get; set; } + /// + /// Gets or sets the front-channel logout URI associated with the application. + /// + public virtual string FrontChannelLogoutUri { get; set; } + /// /// URI to further information about client. /// - public string ClientUri { get; set; } + public virtual string ClientUri { get; set; } /// /// URI to client logo. /// - public string LogoUri { get; set; } + public virtual string LogoUri { get; set; } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/AbpAuthorizationManager.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/AbpAuthorizationManager.cs index d190192058..50ec2866ec 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/AbpAuthorizationManager.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/AbpAuthorizationManager.cs @@ -16,7 +16,7 @@ public class AbpAuthorizationManager : OpenIddictAuthorizationManager cache, [NotNull] [ItemNotNull] ILogger> logger, [NotNull] [ItemNotNull] IOptionsMonitor options, - [NotNull] IOpenIddictAuthorizationStoreResolver resolver, + [NotNull] IOpenIddictAuthorizationStore resolver, AbpOpenIddictIdentifierConverter identifierConverter) : base(cache, logger, options, resolver) { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Scopes/AbpScopeManager.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Scopes/AbpScopeManager.cs index 76596c159b..0f89463733 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Scopes/AbpScopeManager.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Scopes/AbpScopeManager.cs @@ -16,7 +16,7 @@ public class AbpScopeManager : OpenIddictScopeManager [NotNull] [ItemNotNull] IOpenIddictScopeCache cache, [NotNull] [ItemNotNull] ILogger> logger, [NotNull] [ItemNotNull] IOptionsMonitor options, - [NotNull] IOpenIddictScopeStoreResolver resolver, + [NotNull] IOpenIddictScopeStore resolver, AbpOpenIddictIdentifierConverter identifierConverter) : base(cache, logger, options, resolver) { diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/AbpTokenManager.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/AbpTokenManager.cs index 7f4f967124..879d5b3f4f 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/AbpTokenManager.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/AbpTokenManager.cs @@ -16,7 +16,7 @@ public class AbpTokenManager : OpenIddictTokenManager [NotNull] [ItemNotNull] IOpenIddictTokenCache cache, [NotNull] [ItemNotNull] ILogger> logger, [NotNull] [ItemNotNull] IOptionsMonitor options, - [NotNull] IOpenIddictTokenStoreResolver resolver, + [NotNull] IOpenIddictTokenStore resolver, AbpOpenIddictIdentifierConverter identifierConverter) : base(cache, logger, options, resolver) { diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ar.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ar.json index 8146974400..bc1e004895 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ar.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ar.json @@ -16,8 +16,8 @@ "Permission:Edit": "تحرير", "Permission:Delete": "حذف", "Permission:ManageConnectionStrings": "Manage connection strings", - "Permission:ManageFeatures": "إدارة الميزات", + "Permission:ManageFeatures": "الميزات", "DisplayName:AdminEmailAddress": "عنوان البريد الإلكتروني للمسؤول", "DisplayName:AdminPassword": "كلمة مرور المسؤول" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/cs.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/cs.json index 3902ad703b..a563181b61 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/cs.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/cs.json @@ -16,7 +16,7 @@ "Permission:Edit": "Upravit", "Permission:Delete": "Smazat", "Permission:ManageConnectionStrings": "Spravovat connection stringy", - "Permission:ManageFeatures": "Spravovat funkce", + "Permission:ManageFeatures": "Funkce", "DisplayName:AdminEmailAddress": "Email adresa správce", "DisplayName:AdminPassword": "Heslo správce" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/de.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/de.json index 0a62c49a4d..59fd237333 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/de.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/de.json @@ -16,8 +16,8 @@ "Permission:Edit": "Bearbeiten", "Permission:Delete": "Löschen", "Permission:ManageConnectionStrings": "Connection Strings verwalten", - "Permission:ManageFeatures": "Features verwalten", + "Permission:ManageFeatures": "Funktionen", "DisplayName:AdminEmailAddress": "Admin-E-Mail-Adresse", "DisplayName:AdminPassword": "Admin-Passwort" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/el.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/el.json index bc75c34e03..21fbed24bb 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/el.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/el.json @@ -15,7 +15,7 @@ "Permission:Edit": "Επεξεργασία", "Permission:Delete": "Διαγραφή", "Permission:ManageConnectionStrings": "Διαχείριση συμβολοσειρών σύνδεσης", - "Permission:ManageFeatures": "Διαχείριση λειτουργιών", + "Permission:ManageFeatures": "Λειτουργίες", "DisplayName:AdminEmailAddress": "Διεύθυνση email διαχειριστή", "DisplayName:AdminPassword": "Κωδικός διαχειριστή" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en-GB.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en-GB.json index 8b4e4c185b..c53f2be460 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en-GB.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en-GB.json @@ -15,7 +15,7 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:ManageConnectionStrings": "Manage connection strings", - "Permission:ManageFeatures": "Manage features", + "Permission:ManageFeatures": "Features", "DisplayName:AdminEmailAddress": "Admin Email Address", "DisplayName:AdminPassword": "Admin Password" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json index 0ba2b5c42f..1fc5b1985c 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json @@ -16,8 +16,9 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:ManageConnectionStrings": "Manage connection strings", - "Permission:ManageFeatures": "Manage features", + "Permission:ManageFeatures": "Features", "DisplayName:AdminEmailAddress": "Admin Email Address", "DisplayName:AdminPassword": "Admin Password" } } + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/es.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/es.json index bcbfc98852..12ef0a4d31 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/es.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/es.json @@ -16,8 +16,8 @@ "Permission:Edit": "Editar", "Permission:Delete": "Borrar", "Permission:ManageConnectionStrings": "Gestión de cadenas de conexión", - "Permission:ManageFeatures": "Gestión de características", + "Permission:ManageFeatures": "Características", "DisplayName:AdminEmailAddress": "Dirección e-mail de administrador", "DisplayName:AdminPassword": "Contraseña de administrador" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fa.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fa.json index c67a9c070b..7fb481af5e 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fa.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fa.json @@ -15,7 +15,7 @@ "Permission:Edit": "ویرایش", "Permission:Delete": "حذف", "Permission:ManageConnectionStrings": "مدیریت کانکشن استرینگها", - "Permission:ManageFeatures": "مدیریت ویژگی ها", + "Permission:ManageFeatures": "ویژگی‌ها", "DisplayName:AdminEmailAddress": "آدرس ایمیل مدیر", "DisplayName:AdminPassword": "گذرواژه مدیریت" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json index 7721629201..4acd99fd0d 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fi.json @@ -16,8 +16,8 @@ "Permission:Edit": "Muokkaus", "Permission:Delete": "Poisto", "Permission:ManageConnectionStrings": "Hallitse tietokantayhteyksiä", - "Permission:ManageFeatures": "Hallitse ominaisuuksia", + "Permission:ManageFeatures": "Ominaisuudet", "DisplayName:AdminEmailAddress": "Järjestelmänvalvojan sähköpostiosoite", "DisplayName:AdminPassword": "Järjestelmänvalvojan salasana" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fr.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fr.json index 3537ca8ca6..705669c78d 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fr.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/fr.json @@ -16,8 +16,8 @@ "Permission:Edit": "Modifier", "Permission:Delete": "Supprimer", "Permission:ManageConnectionStrings": "Gérer les chaînes de connexion", - "Permission:ManageFeatures": "Gérer les fonctionnalités", + "Permission:ManageFeatures": "Fonctionnalités", "DisplayName:AdminEmailAddress": "Adresse de messagerie d’administrateur", "DisplayName:AdminPassword": "Mot de passe d’administrateur" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hi.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hi.json index 70367305a9..8072bdf6f9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hi.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hi.json @@ -16,8 +16,8 @@ "Permission:Edit": "संपादित करें", "Permission:Delete": "हटाएं", "Permission:ManageConnectionStrings": "कनेक्शन स्ट्रिंग्स प्रबंधित करें", - "Permission:ManageFeatures": "सुविधाओं को प्रबंधित करें", + "Permission:ManageFeatures": "सुविधाएं", "DisplayName:AdminEmailAddress": "ईमेल पता", "DisplayName:AdminPassword": "व्यवस्थापक का पारण शब्द" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hr.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hr.json index 7ca876277f..c646a9a6c1 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hr.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hr.json @@ -16,8 +16,9 @@ "Permission:Edit": "Uredi", "Permission:Delete": "Izbrisati", "Permission:ManageConnectionStrings": "Upravljanje vezom na bazu podataka", - "Permission:ManageFeatures": "Upravljanje značajkama", + "Permission:ManageFeatures": "Značajke", "DisplayName:AdminEmailAddress": "Adresa e-pošte administratora", "DisplayName:AdminPassword": "Administratorska lozinka" } } + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hu.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hu.json index 26787bca90..19085d7a6b 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hu.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/hu.json @@ -16,8 +16,8 @@ "Permission:Edit": "Szerkesztés", "Permission:Delete": "Törlés", "Permission:ManageConnectionStrings": "Kapcsolati beállítások kezelése", - "Permission:ManageFeatures": "Funkciók kezelése", + "Permission:ManageFeatures": "Funkciók", "DisplayName:AdminEmailAddress": "Admin email cím", "DisplayName:AdminPassword": "Admin jelszó" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/is.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/is.json index 5c170f9a27..902383427c 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/is.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/is.json @@ -16,8 +16,8 @@ "Permission:Edit": "Breyta", "Permission:Delete": "Eyða", "Permission:ManageConnectionStrings": "Umsjá tengistrengja", - "Permission:ManageFeatures": "Umsjá eiginleika", + "Permission:ManageFeatures": "Eiginleikar", "DisplayName:AdminEmailAddress": "Netfang stjórnanda (admin)", "DisplayName:AdminPassword": "Lykilorð stjórnanda (admin)" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json index 5d95e2039c..e8c0b866ae 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/it.json @@ -16,8 +16,8 @@ "Permission:Edit": "Modifica", "Permission:Delete": "Elimina", "Permission:ManageConnectionStrings": "Gestisci le stringhe di connessione", - "Permission:ManageFeatures": "Gestisci le funzionalità", + "Permission:ManageFeatures": "Funzionalità", "DisplayName:AdminEmailAddress": "Indirizzo e-mail dell'amministratore", "DisplayName:AdminPassword": "Password dell'amministratore" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/nl.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/nl.json index f739a3eb27..f72e56afe9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/nl.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/nl.json @@ -16,8 +16,8 @@ "Permission:Edit": "Bewerk", "Permission:Delete": "Verwijder", "Permission:ManageConnectionStrings": "Beheer connection strings", - "Permission:ManageFeatures": "Beheer functies", + "Permission:ManageFeatures": "Functies", "DisplayName:AdminEmailAddress": "Admin e-mail adres", "DisplayName:AdminPassword": "Admin wachtwoord" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json index 95a7b39e0e..46f1ca47bf 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json @@ -16,8 +16,8 @@ "Permission:Edit": "Edytuj", "Permission:Delete": "Usuń", "Permission:ManageConnectionStrings": "Zarządzaj connection string'ami", - "Permission:ManageFeatures": "Zarządzaj fukcjami", + "Permission:ManageFeatures": "Funkcje", "DisplayName:AdminEmailAddress": "Adres e-mail administratora", "DisplayName:AdminPassword": "Hasło administratora" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pt-BR.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pt-BR.json index d9f6b14a69..b621cc2069 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pt-BR.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pt-BR.json @@ -16,8 +16,8 @@ "Permission:Edit": "Editar", "Permission:Delete": "Excluir", "Permission:ManageConnectionStrings": "Gerenciar conexões (connection strings)", - "Permission:ManageFeatures": "Gerenciar Funcionalidades", + "Permission:ManageFeatures": "Recursos", "DisplayName:AdminEmailAddress": "Endereço de e-mail do administrador", "DisplayName:AdminPassword": "Senha do administrador" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ro-RO.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ro-RO.json index cb14140ddb..8c7915614d 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ro-RO.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ro-RO.json @@ -16,8 +16,8 @@ "Permission:Edit": "Editează", "Permission:Delete": "Şterge", "Permission:ManageConnectionStrings": "Administrează stringurile de conexiune", - "Permission:ManageFeatures": "Administrarea caracteristicilor", + "Permission:ManageFeatures": "Caracteristici", "DisplayName:AdminEmailAddress": "Adresa de email admin", "DisplayName:AdminPassword": "Parola admin" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ru.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ru.json index 2204cf35cf..f89a39074b 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ru.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/ru.json @@ -16,8 +16,8 @@ "Permission:Edit": "Редактировать", "Permission:Delete": "Удалить", "Permission:ManageConnectionStrings": "Управление строками подключения", - "Permission:ManageFeatures": "Управление функциями", + "Permission:ManageFeatures": "Возможности", "DisplayName:AdminEmailAddress": "Адрес электронной почты администратора", "DisplayName:AdminPassword": "Пароль администратора" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sk.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sk.json index 1aff65e71c..a1479764ba 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sk.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sk.json @@ -16,8 +16,8 @@ "Permission:Edit": "Upraviť", "Permission:Delete": "Zmazať", "Permission:ManageConnectionStrings": "Správa connection stringov", - "Permission:ManageFeatures": "Správa funkcií", + "Permission:ManageFeatures": "Funkcie", "DisplayName:AdminEmailAddress": "Emailová adresa administrátora", "DisplayName:AdminPassword": "Heslo administrátora" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sl.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sl.json index a21cfa702a..b559583420 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sl.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sl.json @@ -16,8 +16,8 @@ "Permission:Edit": "Urejanje", "Permission:Delete": "Brisanje", "Permission:ManageConnectionStrings": "Upravljanje connection string-ov", - "Permission:ManageFeatures": "Upravljanje funkcionalnosti", + "Permission:ManageFeatures": "Lastnosti", "DisplayName:AdminEmailAddress": "Admin e-poštni naslov", "DisplayName:AdminPassword": "Skrbniško geslo" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sv.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sv.json index f112b05001..9cb8c11c89 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sv.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/sv.json @@ -16,8 +16,8 @@ "Permission:Edit": "Redigera", "Permission:Delete": "Radera", "Permission:ManageConnectionStrings": "Hantera anslutningssträngar", - "Permission:ManageFeatures": "Hantera funktioner", + "Permission:ManageFeatures": "Funktioner", "DisplayName:AdminEmailAddress": "Admin E-postadress", "DisplayName:AdminPassword": "Lösenord för administratör" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json index 667a65fb69..24c2292e74 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json @@ -16,8 +16,9 @@ "Permission:Edit": "Düzenleme", "Permission:Delete": "Silme", "Permission:ManageConnectionStrings": "Bağlantı cümlelerini yönet", - "Permission:ManageFeatures": "Özellikleri yönet", + "Permission:ManageFeatures": "Özellikler", "DisplayName:AdminEmailAddress": "Admin Eposta Adresi", "DisplayName:AdminPassword": "Admin Şifresi" } } + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/vi.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/vi.json index 13f5afb095..2ca10f4f05 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/vi.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/vi.json @@ -16,8 +16,8 @@ "Permission:Edit": "Sửa", "Permission:Delete": "Xóa", "Permission:ManageConnectionStrings": "Quản lý chuỗi kết nối", - "Permission:ManageFeatures": "Quản lý các tính năng", + "Permission:ManageFeatures": "Tính năng", "DisplayName:AdminEmailAddress": "Địa chỉ Email Quản trị viên", "DisplayName:AdminPassword": "Mật khẩu quản trị" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json index 8cff1a09b7..d8334301bf 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json @@ -16,8 +16,8 @@ "Permission:Edit": "编辑", "Permission:Delete": "删除", "Permission:ManageConnectionStrings": "管理连接字符串", - "Permission:ManageFeatures": "管理功能", + "Permission:ManageFeatures": "功能", "DisplayName:AdminEmailAddress": "管理员电子邮件地址", "DisplayName:AdminPassword": "管理员密码" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json index 59519aff67..0da3743f2e 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json @@ -16,8 +16,9 @@ "Permission:Edit": "編輯", "Permission:Delete": "刪除", "Permission:ManageConnectionStrings": "管理資料庫連線字串", - "Permission:ManageFeatures": "管理功能", + "Permission:ManageFeatures": "功能", "DisplayName:AdminEmailAddress": "管理者信箱", "DisplayName:AdminPassword": "管理者密碼" } } + diff --git a/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/account.service.ts b/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/account.service.ts index adeb2ee5c9..ad584495c6 100644 --- a/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/account.service.ts +++ b/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/account.service.ts @@ -1,12 +1,14 @@ import type { RegisterDto, ResetPasswordDto, SendPasswordResetCodeDto } from './models'; import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import type { IdentityUserDto } from '../identity/models'; @Injectable({ providedIn: 'root', }) export class AccountService { + private restService = inject(RestService); + apiName = 'AbpAccount'; register = (input: RegisterDto) => @@ -32,6 +34,4 @@ export class AccountService { body: input, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/profile.service.ts b/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/profile.service.ts index 0cea6b8cfb..a14c6cedfd 100644 --- a/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/profile.service.ts +++ b/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/profile.service.ts @@ -1,11 +1,13 @@ import type { ChangePasswordInput, ProfileDto, UpdateProfileDto } from './models'; import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class ProfileService { + private restService = inject(RestService); + apiName = 'AbpAccount'; changePassword = (input: ChangePasswordInput) => @@ -30,6 +32,4 @@ export class ProfileService { body: input, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/web/areas/account/controllers/account.service.ts b/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/web/areas/account/controllers/account.service.ts index 8adf5385b0..52c779655b 100644 --- a/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/web/areas/account/controllers/account.service.ts +++ b/npm/ng-packs/packages/account-core/proxy/src/lib/proxy/account/web/areas/account/controllers/account.service.ts @@ -1,11 +1,13 @@ import type { AbpLoginResult, UserLoginInfo } from './models/models'; import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class AccountService { + private restService = inject(RestService); + apiName = 'AbpAccount'; checkPasswordByLogin = (login: UserLoginInfo) => @@ -30,6 +32,4 @@ export class AccountService { url: '/api/account/logout', }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts b/npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts index 7bad8ef96a..10ca25380b 100644 --- a/npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts +++ b/npm/ng-packs/packages/account-core/src/lib/auth-wrapper.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; @@ -6,6 +6,9 @@ import { ConfigStateService, MultiTenancyService } from '@abp/ng.core'; @Injectable() export class AuthWrapperService { + readonly multiTenancy = inject(MultiTenancyService); + private configState = inject(ConfigStateService); + isMultiTenancyEnabled$ = this.configState.getDeep$('multiTenancy.isEnabled'); get enableLocalLogin$(): Observable { @@ -25,11 +28,9 @@ export class AuthWrapperService { return this.isTenantBoxVisibleForCurrentRoute && this.multiTenancy.isTenantBoxVisible; } - constructor( - public readonly multiTenancy: MultiTenancyService, - private configState: ConfigStateService, - injector: Injector, - ) { + constructor() { + const injector = inject(Injector); + this.route = injector.get(ActivatedRoute); } diff --git a/npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts b/npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts index bcb37c3d48..2890d90776 100644 --- a/npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts +++ b/npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts @@ -5,11 +5,16 @@ import { SessionStateService, } from '@abp/ng.core'; import { ToasterService } from '@abp/ng.theme.shared'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { finalize } from 'rxjs/operators'; @Injectable() export class TenantBoxService { + private toasterService = inject(ToasterService); + private tenantService = inject(AbpTenantService); + private sessionState = inject(SessionStateService); + private configState = inject(ConfigStateService); + currentTenant$ = this.sessionState.getTenant$(); name?: string; @@ -18,13 +23,6 @@ export class TenantBoxService { modalBusy!: boolean; - constructor( - private toasterService: ToasterService, - private tenantService: AbpTenantService, - private sessionState: SessionStateService, - private configState: ConfigStateService, - ) {} - onSwitch() { const tenant = this.sessionState.getTenant(); this.name = tenant?.name || ''; diff --git a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts index 9e1ae788f0..ad5db820ac 100644 --- a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts @@ -1,6 +1,6 @@ import { ProfileService } from '@abp/ng.account.core/proxy'; import { ButtonComponent, getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; -import { Component, Injector, OnInit } from '@angular/core'; +import { Component, Injector, OnInit, inject } from '@angular/core'; import { ReactiveFormsModule, UntypedFormBuilder, @@ -32,6 +32,12 @@ const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword']; export class ChangePasswordComponent implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs { + private fb = inject(UntypedFormBuilder); + private injector = inject(Injector); + private toasterService = inject(ToasterService); + private profileService = inject(ProfileService); + private manageProfileState = inject(ManageProfileStateService); + form!: UntypedFormGroup; inProgress?: boolean; @@ -44,14 +50,6 @@ export class ChangePasswordComponent return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch')); }; - constructor( - private fb: UntypedFormBuilder, - private injector: Injector, - private toasterService: ToasterService, - private profileService: ProfileService, - private manageProfileState: ManageProfileStateService, - ) {} - ngOnInit(): void { this.hideCurrentPassword = !this.manageProfileState.getProfile()?.hasPassword; diff --git a/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts index 815c95d71c..873d45c243 100644 --- a/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts @@ -1,5 +1,5 @@ import { AccountService } from '@abp/ng.account.core/proxy'; -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { ReactiveFormsModule, UntypedFormBuilder, @@ -24,16 +24,16 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; ], }) export class ForgotPasswordComponent { + private fb = inject(UntypedFormBuilder); + private accountService = inject(AccountService); + form: UntypedFormGroup; inProgress?: boolean; isEmailSent = false; - constructor( - private fb: UntypedFormBuilder, - private accountService: AccountService, - ) { + constructor() { this.form = this.fb.group({ email: ['', [Validators.required, Validators.email]], }); diff --git a/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts index 797bb9f492..d7e8c8b98d 100644 --- a/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts @@ -1,7 +1,7 @@ import { ProfileService } from '@abp/ng.account.core/proxy'; import { fadeIn, LoadingDirective } from '@abp/ng.theme.shared'; import { transition, trigger, useAnimation } from '@angular/animations'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { eAccountComponents } from '../../enums/components'; import { ManageProfileStateService } from '../../services/manage-profile.state.service'; import { CommonModule } from '@angular/common'; @@ -33,6 +33,9 @@ import { ChangePasswordComponent } from '../change-password/change-password.comp ], }) export class ManageProfileComponent implements OnInit { + protected profileService = inject(ProfileService); + protected manageProfileState = inject(ManageProfileStateService); + selectedTab = 0; changePasswordKey = eAccountComponents.ChangePassword; @@ -43,11 +46,6 @@ export class ManageProfileComponent implements OnInit { hideChangePasswordTab?: boolean; - constructor( - protected profileService: ProfileService, - protected manageProfileState: ManageProfileStateService, - ) {} - ngOnInit() { this.profileService.get().subscribe(profile => { this.manageProfileState.setProfile(profile); diff --git a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts index ac99a51c23..be6287e954 100644 --- a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { EXTENSIONS_FORM_PROP, FormProp, @@ -24,12 +24,16 @@ import { LocalizationPipe } from '@abp/ng.core'; imports: [ReactiveFormsModule, LocalizationPipe], }) export class PersonalSettingsHalfRowComponent { + private propData = inject(EXTENSIONS_FORM_PROP); + public displayName: string; public name: string; public id: string; public formGroup!: UntypedFormGroup; - constructor(@Inject(EXTENSIONS_FORM_PROP) private propData: FormProp) { + constructor() { + const propData = this.propData; + this.displayName = propData.displayName; this.name = propData.name; this.id = propData.id || ''; diff --git a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts index 18b4935c92..93bbfe26f4 100644 --- a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts @@ -6,7 +6,7 @@ import { LocalizationPipe, } from '@abp/ng.core'; import { ButtonComponent, getPasswordValidators, ToasterService } from '@abp/ng.theme.shared'; -import { Component, Injector, OnInit } from '@angular/core'; +import { Component, Injector, OnInit, inject } from '@angular/core'; import { ReactiveFormsModule, UntypedFormBuilder, @@ -35,6 +35,13 @@ const { maxLength, required, email } = Validators; ], }) export class RegisterComponent implements OnInit { + protected fb = inject(UntypedFormBuilder); + protected accountService = inject(AccountService); + protected configState = inject(ConfigStateService); + protected toasterService = inject(ToasterService); + protected authService = inject(AuthService); + protected injector = inject(Injector); + form!: UntypedFormGroup; inProgress?: boolean; @@ -43,15 +50,6 @@ export class RegisterComponent implements OnInit { authWrapperKey = eAccountComponents.AuthWrapper; - constructor( - protected fb: UntypedFormBuilder, - protected accountService: AccountService, - protected configState: ConfigStateService, - protected toasterService: ToasterService, - protected authService: AuthService, - protected injector: Injector, - ) {} - ngOnInit() { this.init(); this.buildForm(); diff --git a/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts index d04a99ae98..1e8d043000 100644 --- a/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts @@ -1,6 +1,6 @@ import { AccountService } from '@abp/ng.account.core/proxy'; import { ButtonComponent, getPasswordValidators } from '@abp/ng.theme.shared'; -import { Component, Injector, OnInit } from '@angular/core'; +import { Component, Injector, OnInit, inject } from '@angular/core'; import { ReactiveFormsModule, UntypedFormBuilder, @@ -26,6 +26,12 @@ const PASSWORD_FIELDS = ['password', 'confirmPassword']; ], }) export class ResetPasswordComponent implements OnInit { + private fb = inject(UntypedFormBuilder); + private accountService = inject(AccountService); + private route = inject(ActivatedRoute); + private router = inject(Router); + private injector = inject(Injector); + form!: UntypedFormGroup; inProgress = false; @@ -38,14 +44,6 @@ export class ResetPasswordComponent implements OnInit { return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch')); }; - constructor( - private fb: UntypedFormBuilder, - private accountService: AccountService, - private route: ActivatedRoute, - private router: Router, - private injector: Injector, - ) {} - ngOnInit(): void { this.route.queryParams.subscribe(({ userId, resetToken }) => { if (!userId || !resetToken) this.router.navigateByUrl('/account/login'); diff --git a/npm/ng-packs/packages/components/chart.js/src/chart.component.ts b/npm/ng-packs/packages/components/chart.js/src/chart.component.ts index c700a0bce8..9ab84aedcd 100644 --- a/npm/ng-packs/packages/components/chart.js/src/chart.component.ts +++ b/npm/ng-packs/packages/components/chart.js/src/chart.component.ts @@ -1,16 +1,17 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - Input, - OnChanges, - OnDestroy, - Output, - SimpleChanges, - ViewChild, +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + OnChanges, + OnDestroy, + Output, + SimpleChanges, + ViewChild, + inject } from '@angular/core'; let Chart: any; @@ -35,6 +36,9 @@ let Chart: any; exportAs: 'abpChart', }) export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges { + el = inject(ElementRef); + private cdr = inject(ChangeDetectorRef); + @Input() type!: string; @Input() data: any = {}; @@ -57,11 +61,6 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges { chart: any; - constructor( - public el: ElementRef, - private cdr: ChangeDetectorRef, - ) {} - ngAfterViewInit() { import('chart.js/auto').then(module => { Chart = module.default; diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts index 394e125798..465a18b813 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts @@ -1,4 +1,4 @@ -import { Directive, Injector, Input } from '@angular/core'; +import { Directive, Injector, Input, inject } from '@angular/core'; import { ActionData, ActionList, InferredAction } from '../../models/actions'; import { ExtensionsService } from '../../services/extensions.service'; import { EXTENSIONS_ACTION_TYPE, EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token'; @@ -16,7 +16,9 @@ export abstract class AbstractActionsComponent< @Input() record!: InferredData['record']; - protected constructor(injector: Injector) { + protected constructor() { + const injector = inject(Injector); + super(); this.getInjected = injector.get.bind(injector); const extensions = injector.get(ExtensionsService); diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html index 85eb36ffee..9555c58f4b 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html @@ -64,17 +64,24 @@ [prop]="prop.name" [sortable]="prop.sortable" > - + @if (prop.tooltip) { {{ column.name }} } @else { - {{ column.name }} + + {{ column.name }} + } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts index 5307a4a671..0e3d25f26c 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts @@ -1,9 +1,8 @@ -import { - ChangeDetectionStrategy, - Component, - Injector, - Input, - TrackByFunction, +import { + ChangeDetectionStrategy, + Component, + Input, + TrackByFunction, } from '@angular/core'; import { EntityAction, EntityActionList } from '../../models/entity-actions'; import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token'; @@ -43,7 +42,7 @@ export class GridActionsComponent extends AbstractActionsComponent> = (_, item) => item.text; - constructor(injector: Injector) { - super(injector); + constructor() { + super(); } } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts index 7492b6f94e..36b195f933 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/page-toolbar/page-toolbar.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Injector, TrackByFunction } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, TrackByFunction, inject } from '@angular/core'; import { HasCreateInjectorPipe, ToolbarAction, @@ -35,6 +35,8 @@ export class PageToolbarComponent extends AbstractActionsComponent> implements HasCreateInjectorPipe { + readonly injector: Injector; + defaultBtnClass = 'btn btn-sm btn-primary'; getData = () => this.data; @@ -42,8 +44,12 @@ export class PageToolbarComponent readonly trackByFn: TrackByFunction> = (_, item) => item.action || item.component; - constructor(public readonly injector: Injector) { - super(injector); + constructor() { + const injector = inject(Injector); + + super(); + + this.injector = injector; } asToolbarAction(value: ToolbarActionDefault): { value: ToolbarAction } { diff --git a/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts b/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts index 2bba45ff70..b7a05d314b 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts @@ -1,12 +1,13 @@ /* eslint-disable @angular-eslint/no-input-rename */ -import { - Directive, - Injector, - Input, - OnChanges, - OnDestroy, - TemplateRef, - ViewContainerRef, +import { + Directive, + Injector, + Input, + OnChanges, + OnDestroy, + TemplateRef, + ViewContainerRef, + inject } from '@angular/core'; import { PropData, PropList } from '../models/props'; @@ -18,6 +19,9 @@ export class PropDataDirective> extends PropData> implements OnChanges, OnDestroy { + private tempRef = inject>(TemplateRef); + private vcRef = inject(ViewContainerRef); + @Input('abpPropDataFromList') propList?: L; @Input('abpPropDataWithRecord') record!: InferredData['record']; @@ -26,11 +30,9 @@ export class PropDataDirective> readonly getInjected: InferredData['getInjected']; - constructor( - private tempRef: TemplateRef, - private vcRef: ViewContainerRef, - injector: Injector, - ) { + constructor() { + const injector = inject(Injector); + super(); this.getInjected = injector.get.bind(injector); diff --git a/npm/ng-packs/packages/components/page/src/page-part.directive.ts b/npm/ng-packs/packages/components/page/src/page-part.directive.ts index a89302916b..bfaf1e60b6 100644 --- a/npm/ng-packs/packages/components/page/src/page-part.directive.ts +++ b/npm/ng-packs/packages/components/page/src/page-part.directive.ts @@ -1,18 +1,17 @@ -import { - Directive, - TemplateRef, - ViewContainerRef, - Input, - InjectionToken, - Optional, - Inject, - OnInit, - OnDestroy, - Injector, - OnChanges, - SimpleChanges, - SimpleChange, -} from '@angular/core'; +import { + Directive, + TemplateRef, + ViewContainerRef, + Input, + InjectionToken, + OnInit, + OnDestroy, + Injector, + OnChanges, + SimpleChanges, + SimpleChange, + inject + } from '@angular/core'; import { Observable, Subscription, of } from 'rxjs'; export interface PageRenderStrategy { @@ -28,6 +27,11 @@ export const PAGE_RENDER_STRATEGY = new InjectionToken('PAGE selector: '[abpPagePart]', }) export class PagePartDirective implements OnInit, OnDestroy, OnChanges { + private templateRef = inject>(TemplateRef); + private viewContainer = inject(ViewContainerRef); + private renderLogic = inject(PAGE_RENDER_STRATEGY, { optional: true })!; + private injector = inject(Injector); + hasRendered = false; type!: string; subscription!: Subscription; @@ -48,13 +52,6 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges { } }; - constructor( - private templateRef: TemplateRef, - private viewContainer: ViewContainerRef, - @Optional() @Inject(PAGE_RENDER_STRATEGY) private renderLogic: PageRenderStrategy, - private injector: Injector, - ) {} - ngOnChanges({ context }: SimpleChanges): void { if (this.renderLogic?.onContextUpdate) { this.renderLogic.onContextUpdate(context); diff --git a/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts b/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts index 35706bd25f..e7f1bdb913 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts +++ b/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts @@ -1,8 +1,8 @@ -import { Directive, TemplateRef } from '@angular/core'; +import { Directive, TemplateRef, inject } from '@angular/core'; + +@Directive({ + selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]', +}) +export class ExpandedIconTemplateDirective { template = inject>(TemplateRef); -@Directive({ - selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]', -}) -export class ExpandedIconTemplateDirective { - constructor(public template: TemplateRef) {} -} +} diff --git a/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts b/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts index 56c66af04b..70cf228021 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts +++ b/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts @@ -1,8 +1,8 @@ -import { Directive, TemplateRef } from '@angular/core'; +import { Directive, TemplateRef, inject } from '@angular/core'; + +@Directive({ + selector: '[abpTreeNodeTemplate],[abp-tree-node-template]', +}) +export class TreeNodeTemplateDirective { template = inject>(TemplateRef); -@Directive({ - selector: '[abpTreeNodeTemplate],[abp-tree-node-template]', -}) -export class TreeNodeTemplateDirective { - constructor(public template: TemplateRef) {} -} +} diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 549702a1ad..a1961aef4d 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, isDevMode, OnInit, Optional, SkipSelf, Type } from '@angular/core'; +import { Component, inject, isDevMode, OnInit, Type } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; @@ -39,7 +39,9 @@ export class DynamicLayoutComponent implements OnInit { protected readonly routerEvents = inject(RouterEvents); protected readonly environment = inject(EnvironmentService); - constructor(@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent) { + constructor() { + const dynamicLayoutComponent = inject(DynamicLayoutComponent, { optional: true, skipSelf: true })!; + if (dynamicLayoutComponent) { if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.'); return; diff --git a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts index 680c7919a8..9ddc294c2b 100644 --- a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Type } from '@angular/core'; +import { Component, OnInit, Type, inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { distinctUntilChanged } from 'rxjs/operators'; import { ReplaceableComponents } from '../models/replaceable-components'; @@ -15,18 +15,16 @@ import { CommonModule } from '@angular/common'; imports: [CommonModule], }) export class ReplaceableRouteContainerComponent implements OnInit { + private route = inject(ActivatedRoute); + private replaceableComponents = inject(ReplaceableComponentsService); + private subscription = inject(SubscriptionService); + defaultComponent!: Type; componentKey!: string; externalComponent?: Type; - constructor( - private route: ActivatedRoute, - private replaceableComponents: ReplaceableComponentsService, - private subscription: SubscriptionService, - ) {} - ngOnInit() { this.defaultComponent = this.route.snapshot.data.replaceableComponent.defaultComponent; this.componentKey = ( diff --git a/npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts index f34d187939..398eaef9cc 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts @@ -1,9 +1,11 @@ -import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core'; +import { AfterViewInit, Directive, ElementRef, Input, inject } from '@angular/core'; @Directive({ selector: '[autofocus]', }) export class AutofocusDirective implements AfterViewInit { + private elRef = inject(ElementRef); + private _delay = 0; @Input('autofocus') @@ -15,8 +17,6 @@ export class AutofocusDirective implements AfterViewInit { return this._delay; } - constructor(private elRef: ElementRef) {} - ngAfterViewInit(): void { setTimeout(() => this.elRef.nativeElement.focus(), this.delay as number); } diff --git a/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts index 204db6b418..239aed688d 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Directive, ElementRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { fromEvent } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { SubscriptionService } from '../services/subscription.service'; @@ -8,15 +8,13 @@ import { SubscriptionService } from '../services/subscription.service'; providers: [SubscriptionService], }) export class InputEventDebounceDirective implements OnInit { + private el = inject(ElementRef); + private subscription = inject(SubscriptionService); + @Input() debounce = 300; @Output('input.debounce') readonly debounceEvent = new EventEmitter(); - constructor( - private el: ElementRef, - private subscription: SubscriptionService, - ) {} - ngOnInit(): void { const input$ = fromEvent(this.el.nativeElement, 'input').pipe( debounceTime(this.debounce), diff --git a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts index 1e37e1c908..7b3bd0319b 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts @@ -1,15 +1,16 @@ -import { - Directive, - EmbeddedViewRef, - Input, - IterableChangeRecord, - IterableChanges, - IterableDiffer, - IterableDiffers, - OnChanges, - TemplateRef, - TrackByFunction, - ViewContainerRef, +import { + Directive, + EmbeddedViewRef, + Input, + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDiffers, + OnChanges, + TemplateRef, + TrackByFunction, + ViewContainerRef, + inject } from '@angular/core'; import clone from 'just-clone'; import compare from 'just-compare'; @@ -36,6 +37,10 @@ class RecordView { selector: '[abpFor]', }) export class ForDirective implements OnChanges { + private tempRef = inject>(TemplateRef); + private vcRef = inject(ViewContainerRef); + private differs = inject(IterableDiffers); + // eslint-disable-next-line @angular-eslint/no-input-rename @Input('abpForOf') items!: any[]; @@ -73,12 +78,6 @@ export class ForDirective implements OnChanges { return this.trackBy || ((index: number, item: any) => (item as any).id || index); } - constructor( - private tempRef: TemplateRef, - private vcRef: ViewContainerRef, - private differs: IterableDiffers, - ) {} - private iterateOverAppliedOperations(changes: IterableChanges) { const rw: RecordView[] = []; diff --git a/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts index 25972ccb71..879fbdf780 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts @@ -1,12 +1,12 @@ -import { - ChangeDetectorRef, - Directive, - ElementRef, - EventEmitter, - Input, - OnInit, - Output, - Self, +import { + ChangeDetectorRef, + Directive, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + inject } from '@angular/core'; import { FormGroupDirective, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { fromEvent } from 'rxjs'; @@ -22,6 +22,11 @@ type Controls = { [key: string]: UntypedFormControl } | UntypedFormGroup[]; providers: [SubscriptionService], }) export class FormSubmitDirective implements OnInit { + private formGroupDirective = inject(FormGroupDirective, { self: true }); + private host = inject>(ElementRef); + private cdRef = inject(ChangeDetectorRef); + private subscription = inject(SubscriptionService); + @Input() debounce = 200; @@ -36,13 +41,6 @@ export class FormSubmitDirective implements OnInit { executedNgSubmit = false; - constructor( - @Self() private formGroupDirective: FormGroupDirective, - private host: ElementRef, - private cdRef: ChangeDetectorRef, - private subscription: SubscriptionService, - ) {} - ngOnInit() { this.subscription.addOne(this.formGroupDirective.ngSubmit, () => { if (this.markAsDirtyWhenSubmit) { diff --git a/npm/ng-packs/packages/core/src/lib/directives/init.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/init.directive.ts index 293384e4e1..1271f031ba 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/init.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/init.directive.ts @@ -1,12 +1,12 @@ -import { Directive, Output, EventEmitter, ElementRef, AfterViewInit } from '@angular/core'; +import { Directive, Output, EventEmitter, ElementRef, AfterViewInit, inject } from '@angular/core'; @Directive({ selector: '[abpInit]', }) export class InitDirective implements AfterViewInit { - @Output('abpInit') readonly init = new EventEmitter>(); + private elRef = inject(ElementRef); - constructor(private elRef: ElementRef) {} + @Output('abpInit') readonly init = new EventEmitter>(); ngAfterViewInit() { this.init.emit(this.elRef); diff --git a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts index f277bf50ee..50b805dba4 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts @@ -1,14 +1,13 @@ -import { - AfterViewInit, - ChangeDetectorRef, - Directive, - Inject, - Input, - OnChanges, - OnDestroy, - Optional, - TemplateRef, - ViewContainerRef, +import { + AfterViewInit, + ChangeDetectorRef, + Directive, + Input, + OnChanges, + OnDestroy, + TemplateRef, + ViewContainerRef, + inject } from '@angular/core'; import { ReplaySubject, Subscription } from 'rxjs'; import { distinctUntilChanged, take } from 'rxjs/operators'; @@ -20,6 +19,12 @@ import { QueueManager } from '../utils/queue'; selector: '[abpPermission]', }) export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit { + private templateRef = inject>(TemplateRef, { optional: true })!; + private vcRef = inject(ViewContainerRef); + private permissionService = inject(PermissionService); + private cdRef = inject(ChangeDetectorRef); + queue = inject(QUEUE_MANAGER); + @Input('abpPermission') condition: string | undefined; @Input('abpPermissionRunChangeDetection') runChangeDetection = true; @@ -30,14 +35,6 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit rendered = false; - constructor( - @Optional() private templateRef: TemplateRef, - private vcRef: ViewContainerRef, - private permissionService: PermissionService, - private cdRef: ChangeDetectorRef, - @Inject(QUEUE_MANAGER) public queue: QueueManager, - ) {} - private check() { if (this.subscription) { this.subscription.unsubscribe(); diff --git a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts index 9a1bedcd71..7e948cc20f 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts @@ -1,13 +1,14 @@ -import { - Directive, - Injector, - Input, - OnChanges, - OnInit, - SimpleChanges, - TemplateRef, - Type, - ViewContainerRef, +import { + Directive, + Injector, + Input, + OnChanges, + OnInit, + SimpleChanges, + TemplateRef, + Type, + ViewContainerRef, + inject } from '@angular/core'; import compare from 'just-compare'; import { Subscription } from 'rxjs'; @@ -22,6 +23,12 @@ import { SubscriptionService } from '../services/subscription.service'; providers: [SubscriptionService], }) export class ReplaceableTemplateDirective implements OnInit, OnChanges { + private injector = inject(Injector); + private templateRef = inject>(TemplateRef); + private vcRef = inject(ViewContainerRef); + private replaceableComponents = inject(ReplaceableComponentsService); + private subscription = inject(SubscriptionService); + @Input('abpReplaceableTemplate') data!: ReplaceableComponents.ReplaceableTemplateDirectiveInput; @@ -40,13 +47,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges { initialized = false; - constructor( - private injector: Injector, - private templateRef: TemplateRef, - private vcRef: ViewContainerRef, - private replaceableComponents: ReplaceableComponentsService, - private subscription: SubscriptionService, - ) { + constructor() { this.context = { initTemplate: (ref: any) => { this.resetDefaultComponent(); diff --git a/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts index ddc92cb56b..a1af9bf07e 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, EventEmitter, OnInit, Output } from '@angular/core'; +import { Directive, ElementRef, EventEmitter, OnInit, Output, inject } from '@angular/core'; import { fromEvent } from 'rxjs'; import { SubscriptionService } from '../services/subscription.service'; @@ -7,12 +7,10 @@ import { SubscriptionService } from '../services/subscription.service'; providers: [SubscriptionService], }) export class StopPropagationDirective implements OnInit { - @Output('click.stop') readonly stopPropEvent = new EventEmitter(); + private el = inject(ElementRef); + private subscription = inject(SubscriptionService); - constructor( - private el: ElementRef, - private subscription: SubscriptionService, - ) {} + @Output('click.stop') readonly stopPropEvent = new EventEmitter(); ngOnInit(): void { this.subscription.addOne(fromEvent(this.el.nativeElement, 'click'), event => { diff --git a/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts b/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts index 76dc66b3ae..d8880b4fbf 100644 --- a/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts +++ b/npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts @@ -1,4 +1,4 @@ -import { Injectable, Optional } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Route, Router } from '@angular/router'; import { ABP } from '../models'; import { RoutesService } from '../services/routes.service'; @@ -7,10 +7,10 @@ import { RoutesService } from '../services/routes.service'; providedIn: 'root', }) export class RoutesHandler { - constructor( - private routes: RoutesService, - @Optional() private router: Router, - ) { + private routes = inject(RoutesService); + private router = inject(Router, { optional: true })!; + + constructor() { this.addRoutes(); } diff --git a/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts b/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts index f5fc270e7c..b0d8d5e0b5 100644 --- a/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts +++ b/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts @@ -5,7 +5,7 @@ import { HttpRequest, HttpEvent, } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { finalize } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { HttpWaitService } from '../services'; @@ -14,7 +14,7 @@ import { HttpWaitService } from '../services'; providedIn: 'root', }) export class ApiInterceptor implements IApiInterceptor { - constructor(private httpWaitService: HttpWaitService) {} + private httpWaitService = inject(HttpWaitService); getAdditionalHeaders(existingHeaders?: HttpHeaders) { return existingHeaders || new HttpHeaders(); diff --git a/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts index 2dbac68eb7..bbb2a71752 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/localization.pipe.ts @@ -1,4 +1,4 @@ -import { Injectable, Pipe, PipeTransform } from '@angular/core'; +import { Injectable, Pipe, PipeTransform, inject } from '@angular/core'; import { LocalizationWithDefault } from '../models/localization'; import { LocalizationService } from '../services/localization.service'; @@ -7,7 +7,8 @@ import { LocalizationService } from '../services/localization.service'; name: 'abpLocalization', }) export class LocalizationPipe implements PipeTransform { - constructor(private localization: LocalizationService) {} + private localization = inject(LocalizationService); + transform( value: string | LocalizationWithDefault = '', diff --git a/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts index b72ca45dde..8ed6dc55b4 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts @@ -1,5 +1,5 @@ import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common'; -import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core'; +import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core'; import { ConfigStateService } from '../services'; import { getShortDateShortTimeFormat } from '../utils/date-utils'; @@ -8,11 +8,12 @@ import { getShortDateShortTimeFormat } from '../utils/date-utils'; pure: true, }) export class ShortDateTimePipe extends DatePipe implements PipeTransform { - constructor( - private configStateService: ConfigStateService, - @Inject(LOCALE_ID) locale: string, - @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null, - ) { + private configStateService = inject(ConfigStateService); + + constructor() { + const locale = inject(LOCALE_ID); + const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true }); + super(locale, defaultTimezone); } diff --git a/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts index 26a7e1552f..9968009e49 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts @@ -1,5 +1,5 @@ import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common'; -import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core'; +import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core'; import { ConfigStateService } from '../services'; import { getShortDateFormat } from '../utils/date-utils'; @@ -8,11 +8,12 @@ import { getShortDateFormat } from '../utils/date-utils'; pure: true, }) export class ShortDatePipe extends DatePipe implements PipeTransform { - constructor( - private configStateService: ConfigStateService, - @Inject(LOCALE_ID) locale: string, - @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null, - ) { + private configStateService = inject(ConfigStateService); + + constructor() { + const locale = inject(LOCALE_ID); + const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true }); + super(locale, defaultTimezone); } diff --git a/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts index 54e70802f8..fac722af5e 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts @@ -1,5 +1,5 @@ import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common'; -import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core'; +import { LOCALE_ID, Pipe, PipeTransform, inject } from '@angular/core'; import { ConfigStateService } from '../services'; import { getShortTimeFormat } from '../utils/date-utils'; @@ -8,11 +8,12 @@ import { getShortTimeFormat } from '../utils/date-utils'; pure: true, }) export class ShortTimePipe extends DatePipe implements PipeTransform { - constructor( - private configStateService: ConfigStateService, - @Inject(LOCALE_ID) locale: string, - @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string | null, - ) { + private configStateService = inject(ConfigStateService); + + constructor() { + const locale = inject(LOCALE_ID); + const defaultTimezone = inject(DATE_PIPE_DEFAULT_TIMEZONE, { optional: true }); + super(locale, defaultTimezone); } diff --git a/npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts index 5a1ac4df29..77663a1262 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/to-injector.pipe.ts @@ -1,4 +1,4 @@ -import { InjectionToken, Injector, Pipe, PipeTransform } from '@angular/core'; +import { InjectionToken, Injector, Pipe, PipeTransform, inject } from '@angular/core'; export const INJECTOR_PIPE_DATA_TOKEN = new InjectionToken( 'INJECTOR_PIPE_DATA_TOKEN', @@ -8,7 +8,8 @@ export const INJECTOR_PIPE_DATA_TOKEN = new InjectionToken( name: 'toInjector', }) export class ToInjectorPipe implements PipeTransform { - constructor(private injector: Injector) {} + private injector = inject(Injector); + transform( value: any, token: InjectionToken = INJECTOR_PIPE_DATA_TOKEN, diff --git a/npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts b/npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts index ca90c4bc33..9d34802da1 100644 --- a/npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts +++ b/npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts @@ -1,10 +1,12 @@ -import { LOCALE_ID, Provider } from '@angular/core'; +import { LOCALE_ID, Provider, inject } from '@angular/core'; import { differentLocales } from '../constants/different-locales'; import { LocalizationService } from '../services/localization.service'; import { checkHasProp } from '../utils/common-utils'; export class LocaleId extends String { - constructor(private localizationService: LocalizationService) { + private localizationService = inject(LocalizationService); + + constructor() { super(); } @@ -24,5 +26,4 @@ export class LocaleId extends String { export const LocaleProvider: Provider = { provide: LOCALE_ID, useClass: LocaleId, - deps: [LocalizationService], -}; +}; \ No newline at end of file diff --git a/npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts b/npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts index 039ddc707e..d778e0a279 100644 --- a/npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts +++ b/npm/ng-packs/packages/core/src/lib/proxy/pages/abp/multi-tenancy/abp-tenant.service.ts @@ -1,12 +1,14 @@ import { RestService } from '../../../../services'; import { Rest } from '../../../../models'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import type { FindTenantResultDto } from '../../../volo/abp/asp-net-core/mvc/multi-tenancy/models'; @Injectable({ providedIn: 'root', }) -export class AbpTenantService { +export class AbpTenantService { + private restService = inject(RestService); + apiName = 'abp'; @@ -24,6 +26,4 @@ export class AbpTenantService { url: `/api/abp/multi-tenancy/tenants/by-name/${name}`, }, { apiName: this.apiName,...config }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts b/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts index a331106214..770faca476 100644 --- a/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts +++ b/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/api-exploring/abp-api-definition.service.ts @@ -1,12 +1,14 @@ import { RestService } from '../../../../../../services'; import { Rest } from '../../../../../../models'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import type { ApplicationApiDescriptionModel, ApplicationApiDescriptionModelRequestDto } from '../../../http/modeling/models'; @Injectable({ providedIn: 'root', }) -export class AbpApiDefinitionService { +export class AbpApiDefinitionService { + private restService = inject(RestService); + apiName = 'abp'; @@ -17,6 +19,4 @@ export class AbpApiDefinitionService { params: { includeTypes: model.includeTypes }, }, { apiName: this.apiName,...config }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts b/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts index 3c04af51fc..9dc35fa7b6 100644 --- a/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts +++ b/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service.ts @@ -1,12 +1,14 @@ import type { ApplicationConfigurationDto, ApplicationConfigurationRequestOptions } from './models'; import { RestService } from '../../../../../../services'; import { Rest } from '../../../../../../models'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) -export class AbpApplicationConfigurationService { +export class AbpApplicationConfigurationService { + private restService = inject(RestService); + apiName = 'abp'; @@ -17,6 +19,4 @@ export class AbpApplicationConfigurationService { params: { includeLocalizationResources: options.includeLocalizationResources }, }, { apiName: this.apiName, ...config }); - - constructor(private restService: RestService) { } } diff --git a/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts b/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts index 3b9fd80584..aa3f495b53 100644 --- a/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service.ts @@ -1,12 +1,14 @@ import type { ApplicationLocalizationDto, ApplicationLocalizationRequestDto } from './models'; import { RestService } from '../../../../../../services'; import { Rest } from '../../../../../../models'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) -export class AbpApplicationLocalizationService { +export class AbpApplicationLocalizationService { + private restService = inject(RestService); + apiName = 'abp'; @@ -17,6 +19,4 @@ export class AbpApplicationLocalizationService { params: { cultureName: input.cultureName, onlyDynamics: input.onlyDynamics }, }, { apiName: this.apiName,...config }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index 475f6d76f3..1243e8b2ba 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Optional } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { map, switchMap, take, tap } from 'rxjs/operators'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; @@ -15,6 +15,10 @@ import { InternalStore } from '../utils/internal-store-utils'; providedIn: 'root', }) export class ConfigStateService { + private abpConfigService = inject(AbpApplicationConfigurationService); + private abpApplicationLocalizationService = inject(AbpApplicationLocalizationService); + private readonly includeLocalizationResources = inject(INCUDE_LOCALIZATION_RESOURCES_TOKEN, { optional: true }); + private updateSubject = new Subject(); private readonly store = new InternalStore({} as ApplicationConfigurationDto); @@ -27,13 +31,7 @@ export class ConfigStateService { get createOnUpdateStream() { return this.store.sliceUpdate; } - constructor( - private abpConfigService: AbpApplicationConfigurationService, - private abpApplicationLocalizationService: AbpApplicationLocalizationService, - @Optional() - @Inject(INCUDE_LOCALIZATION_RESOURCES_TOKEN) - private readonly includeLocalizationResources: boolean | null, - ) { + constructor() { this.initUpdateStream(); } diff --git a/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts b/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts index dfcdea330a..d9aac2bd48 100644 --- a/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/content-projection.service.ts @@ -1,9 +1,10 @@ -import { Injectable, Injector, TemplateRef, Type } from '@angular/core'; +import { Injectable, Injector, TemplateRef, Type, inject } from '@angular/core'; import { ProjectionStrategy } from '../strategies/projection.strategy'; @Injectable({ providedIn: 'root' }) export class ContentProjectionService { - constructor(private injector: Injector) {} + private injector = inject(Injector); + projectContent | TemplateRef>( projectionStrategy: ProjectionStrategy, diff --git a/npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts b/npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts index d80599fc25..d8073d8760 100644 --- a/npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { HttpRequest } from '@angular/common/http'; import { InternalStore } from '../utils/internal-store-utils'; import { getPathName } from '../utils/http-utils'; @@ -26,7 +26,9 @@ export class HttpWaitService { private delay: number; private destroy$ = new Subject(); - constructor(injector: Injector) { + constructor() { + const injector = inject(Injector); + this.delay = injector.get(LOADER_DELAY, 500); } diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts index f50102e15a..8358570db9 100644 --- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { concat, Observable, of, pipe, throwError } from 'rxjs'; import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators'; import { LoadingStrategy } from '../strategies'; @@ -8,9 +8,9 @@ import { ResourceWaitService } from './resource-wait.service'; providedIn: 'root', }) export class LazyLoadService { - readonly loaded = new Map(); + private resourceWaitService = inject(ResourceWaitService); - constructor(private resourceWaitService: ResourceWaitService) {} + readonly loaded = new Map(); load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable { if (this.loaded.has(strategy.path)) return of(new CustomEvent('load')); diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index 204833a66d..3cfc0ecac9 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector, OnDestroy } from '@angular/core'; +import { Injectable, Injector, OnDestroy, inject } from '@angular/core'; import { EMPTY, BehaviorSubject, @@ -119,7 +119,9 @@ export class ListService implements this.next(); }; - constructor(injector: Injector) { + constructor() { + const injector = inject(Injector); + const delay = injector.get(LIST_QUERY_DEBOUNCE_TIME, 300); this.delay = delay ? debounceTime(delay) : tap(); this.get(); diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 7b474e7881..dd6def0c24 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -1,5 +1,5 @@ import { registerLocaleData } from '@angular/common'; -import { Injectable, Injector, isDevMode, Optional, SkipSelf } from '@angular/core'; +import { Injectable, Injector, isDevMode, inject } from '@angular/core'; import { BehaviorSubject, combineLatest, from, Observable, Subject } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; import { ABP } from '../models/common'; @@ -17,6 +17,10 @@ import { SessionStateService } from './session-state.service'; @Injectable({ providedIn: 'root' }) export class LocalizationService { + private sessionState = inject(SessionStateService); + private injector = inject(Injector); + private configState = inject(ConfigStateService); + private latestLang = this.sessionState.getLanguage(); private _languageChange$ = new Subject(); @@ -44,14 +48,9 @@ export class LocalizationService { return this._languageChange$.asObservable(); } - constructor( - private sessionState: SessionStateService, - private injector: Injector, - @Optional() - @SkipSelf() - otherInstance: LocalizationService, - private configState: ConfigStateService, - ) { + constructor() { + const otherInstance = inject(LocalizationService, { optional: true, skipSelf: true })!; + if (otherInstance) throw new Error('LocalizationService should have only one instance.'); this.listenToSetLanguage(); diff --git a/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts b/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts index 8fbde5cc8e..a88cba1c3d 100644 --- a/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { map, switchMap } from 'rxjs/operators'; import { AbpTenantService } from '../proxy/pages/abp/multi-tenancy'; import { @@ -7,11 +7,15 @@ import { } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models'; import { TENANT_KEY } from '../tokens/tenant-key.token'; import { ConfigStateService } from './config-state.service'; -import { RestService } from './rest.service'; import { SessionStateService } from './session-state.service'; @Injectable({ providedIn: 'root' }) export class MultiTenancyService { + private sessionState = inject(SessionStateService); + private tenantService = inject(AbpTenantService); + private configStateService = inject(ConfigStateService); + tenantKey = inject(TENANT_KEY); + domainTenant: CurrentTenantDto | null = null; isTenantBoxVisible = true; @@ -23,14 +27,6 @@ export class MultiTenancyService { return this.configStateService.refreshAppState().pipe(map(_ => tenant)); }; - constructor( - private restService: RestService, - private sessionState: SessionStateService, - private tenantService: AbpTenantService, - private configStateService: ConfigStateService, - @Inject(TENANT_KEY) public tenantKey: string, - ) { } - setTenantByName(tenantName: string) { return this.tenantService .findTenantByName(tenantName) diff --git a/npm/ng-packs/packages/core/src/lib/services/permission.service.ts b/npm/ng-packs/packages/core/src/lib/services/permission.service.ts index 70724ac814..b45b2a36f5 100644 --- a/npm/ng-packs/packages/core/src/lib/services/permission.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/permission.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { map } from 'rxjs/operators'; import { ABP } from '../models/common'; import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models'; @@ -6,7 +6,8 @@ import { ConfigStateService } from './config-state.service'; @Injectable({ providedIn: 'root' }) export class PermissionService { - constructor(protected configState: ConfigStateService) {} + protected configState = inject(ConfigStateService); + getGrantedPolicy$(key: string) { return this.getStream().pipe( diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index 1f02daa389..c156a237aa 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NgZone } from '@angular/core'; +import { Injectable, NgZone, inject } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -8,6 +8,9 @@ import { reloadRoute } from '../utils/route-utils'; @Injectable({ providedIn: 'root' }) export class ReplaceableComponentsService { + private ngZone = inject(NgZone); + private router = inject(Router); + private readonly store: InternalStore; get replaceableComponents$(): Observable { @@ -22,7 +25,7 @@ export class ReplaceableComponentsService { return this.store.sliceUpdate(state => state); } - constructor(private ngZone: NgZone, private router: Router) { + constructor() { this.store = new InternalStore([] as ReplaceableComponents.ReplaceableComponent[]); } diff --git a/npm/ng-packs/packages/core/src/lib/services/rest.service.ts b/npm/ng-packs/packages/core/src/lib/services/rest.service.ts index 83f91777ca..a331b6c742 100644 --- a/npm/ng-packs/packages/core/src/lib/services/rest.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/rest.service.ts @@ -1,5 +1,5 @@ import { HttpClient, HttpParameterCodec, HttpParams, HttpRequest } from '@angular/common/http'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { ExternalHttpClient } from '../clients/http.client'; @@ -14,13 +14,12 @@ import { HttpErrorReporterService } from './http-error-reporter.service'; providedIn: 'root', }) export class RestService { - constructor( - @Inject(CORE_OPTIONS) protected options: ABP.Root, - protected http: HttpClient, - protected externalHttp: ExternalHttpClient, - protected environment: EnvironmentService, - protected httpErrorReporter: HttpErrorReporterService, - ) { } + protected options = inject(CORE_OPTIONS); + protected http = inject(HttpClient); + protected externalHttp = inject(ExternalHttpClient); + protected environment = inject(EnvironmentService); + protected httpErrorReporter = inject(HttpErrorReporterService); + protected getApiFromStore(apiName: string | undefined): string { return this.environment.getApiUrl(apiName); diff --git a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts index 96e66a00e3..f68f6d065a 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-wait.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { NavigationStart } from '@angular/router'; import { of, Subject, timer } from 'rxjs'; import { map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators'; @@ -14,10 +14,14 @@ export interface RouterWaitState { providedIn: 'root', }) export class RouterWaitService { + private routerEvents = inject(RouterEvents); + private store = new InternalStore({ loading: false }); private destroy$ = new Subject(); private delay: number; - constructor(private routerEvents: RouterEvents, injector: Injector) { + constructor() { + const injector = inject(Injector); + this.delay = injector.get(LOADER_DELAY, 500); this.updateLoadingStatusOnNavigationEvents(); } diff --git a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts index 393c724423..175bf76446 100644 --- a/npm/ng-packs/packages/core/src/lib/services/routes.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/routes.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector, OnDestroy } from '@angular/core'; +import { Injectable, Injector, OnDestroy, inject } from '@angular/core'; import { BehaviorSubject, Observable, Subscription, map } from 'rxjs'; import { ABP } from '../models/common'; import { OTHERS_GROUP } from '../tokens'; @@ -201,6 +201,8 @@ export abstract class AbstractNavTreeService extends AbstractTreeService implements OnDestroy { + protected injector = inject(Injector); + private subscription: Subscription; private permissionService: PermissionService; private compareFunc; @@ -211,8 +213,10 @@ export abstract class AbstractNavTreeService return this.compareFunc(a, b); }; - constructor(protected injector: Injector) { + constructor() { super(); + const injector = this.injector; + const configState = this.injector.get(ConfigStateService); this.subscription = configState .createOnUpdateStream(state => state) diff --git a/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts index d6f423983c..7e519dc742 100644 --- a/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/session-state.service.ts @@ -12,6 +12,9 @@ import { AbpLocalStorageService } from './local-storage.service'; providedIn: 'root', }) export class SessionStateService { + private configState = inject(ConfigStateService); + private localStorageService = inject(AbpLocalStorageService); + private readonly store = new InternalStore({} as Session.State); protected readonly document = inject(DOCUMENT); @@ -19,10 +22,7 @@ export class SessionStateService { this.localStorageService.setItem('abpSession', JSON.stringify(this.store.state)); }; - constructor( - private configState: ConfigStateService, - private localStorageService: AbpLocalStorageService, - ) { + constructor() { this.init(); this.setInitialLanguage(); } diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 411a9e8041..aac6c41fcd 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -1,201 +1,201 @@ -import { HttpClient } from '@angular/common/http'; -import { Component, NgModule } from '@angular/core'; -import { ActivatedRoute, RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; -import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; -import { eLayoutType } from '../enums/common'; -import { ABP } from '../models'; -import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; -import { ReplaceableComponentsService, RoutesService } from '../services'; -import { mockRoutesService } from './routes.service.spec'; - -@Component({ - selector: 'abp-layout-application', - template: '', -}) -class DummyApplicationLayoutComponent {} - -@Component({ - selector: 'abp-layout-account', - template: '', -}) -class DummyAccountLayoutComponent {} - -@Component({ - selector: 'abp-layout-empty', - template: '', -}) -class DummyEmptyLayoutComponent {} - -const LAYOUTS = [ - DummyApplicationLayoutComponent, - DummyAccountLayoutComponent, - DummyEmptyLayoutComponent, -]; - -@NgModule({ - imports: [RouterModule], - declarations: [...LAYOUTS], -}) -class DummyLayoutModule {} - -@Component({ - selector: 'abp-dummy', - template: '{{route.snapshot.data?.name}} works!', -}) -class DummyComponent { - constructor(public route: ActivatedRoute) {} -} - -const routes: ABP.Route[] = [ - { - path: '', - name: 'Root', - }, - { - path: '/parentWithLayout', - name: 'ParentWithLayout', - parentName: 'Root', - layout: eLayoutType.application, - }, - { - path: '/parentWithLayout/childWithoutLayout', - name: 'ChildWithoutLayout', - parentName: 'ParentWithLayout', - }, - { - path: '/parentWithLayout/childWithLayout', - name: 'ChildWithLayout', - parentName: 'ParentWithLayout', - layout: eLayoutType.account, - }, - { - path: '/withData', - name: 'WithData', - layout: eLayoutType.application, - }, -]; - -describe('DynamicLayoutComponent', () => { - const createComponent = createRoutingFactory({ - component: RouterOutletComponent, - stubsEnabled: false, - declarations: [DummyComponent, DynamicLayoutComponent], - mocks: [AbpApplicationConfigurationService, HttpClient], - providers: [ - { - provide: RoutesService, - useFactory: () => mockRoutesService(), - }, - ReplaceableComponentsService, - ], - imports: [RouterModule, DummyLayoutModule], - routes: [ - { path: '', component: RouterOutletComponent }, - { - path: 'parentWithLayout', - component: DynamicLayoutComponent, - children: [ - { - path: 'childWithoutLayout', - component: DummyComponent, - data: { name: 'childWithoutLayout' }, - }, - { - path: 'childWithLayout', - component: DummyComponent, - data: { name: 'childWithLayout' }, - }, - ], - }, - { - path: 'withData', - component: DynamicLayoutComponent, - children: [ - { - path: '', - component: DummyComponent, - data: { name: 'withData' }, - }, - ], - data: { layout: eLayoutType.empty }, - }, - { - path: 'withoutLayout', - component: DynamicLayoutComponent, - children: [ - { - path: '', - component: DummyComponent, - data: { name: 'withoutLayout' }, - }, - ], - data: { layout: null }, - }, - ], - }); - - let spectator: SpectatorRouting; - let replaceableComponents: ReplaceableComponentsService; - - beforeEach(async () => { - spectator = createComponent(); - replaceableComponents = spectator.inject(ReplaceableComponentsService); - const routesService = spectator.inject(RoutesService); - routesService.add(routes); - - replaceableComponents.add({ - key: 'Theme.ApplicationLayoutComponent', - component: DummyApplicationLayoutComponent, - }); - replaceableComponents.add({ - key: 'Theme.AccountLayoutComponent', - component: DummyAccountLayoutComponent, - }); - replaceableComponents.add({ - key: 'Theme.EmptyLayoutComponent', - component: DummyEmptyLayoutComponent, - }); - }); - - it('should handle application layout from parent abp route and display it', async () => { - spectator.router.navigateByUrl('/parentWithLayout/childWithoutLayout'); - await spectator.fixture.whenStable(); - spectator.detectComponentChanges(); - expect(spectator.query('abp-dynamic-layout')).toBeTruthy(); - expect(spectator.query('abp-layout-application')).toBeTruthy(); - }); - - it('should handle account layout from own property and display it', async () => { - spectator.router.navigateByUrl('/parentWithLayout/childWithLayout'); - await spectator.fixture.whenStable(); - spectator.detectComponentChanges(); - expect(spectator.query('abp-layout-account')).toBeTruthy(); - }); - - it('should handle empty layout from route data and display it', async () => { - spectator.router.navigateByUrl('/withData'); - await spectator.fixture.whenStable(); - spectator.detectComponentChanges(); - expect(spectator.query('abp-layout-empty')).toBeTruthy(); - }); - - it('should display empty layout when layout is null', async () => { - spectator.router.navigateByUrl('/withoutLayout'); - await spectator.fixture.whenStable(); - spectator.detectComponentChanges(); - expect(spectator.query('abp-layout-empty')).toBeTruthy(); - }); - - it('should not display any layout when layouts are empty', async () => { - const spy = jest.spyOn(replaceableComponents, 'get'); - spy.mockReturnValue(null); - spectator.detectChanges(); - - spectator.router.navigateByUrl('/withoutLayout'); - await spectator.fixture.whenStable(); - spectator.detectComponentChanges(); - - expect(spectator.query('abp-layout-empty')).toBeFalsy(); - }); -}); +import { HttpClient } from '@angular/common/http'; +import { Component, NgModule, inject as inject_1 } from '@angular/core'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; +import { eLayoutType } from '../enums/common'; +import { ABP } from '../models'; +import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; +import { ReplaceableComponentsService, RoutesService } from '../services'; +import { mockRoutesService } from './routes.service.spec'; + +@Component({ + selector: 'abp-layout-application', + template: '', +}) +class DummyApplicationLayoutComponent {} + +@Component({ + selector: 'abp-layout-account', + template: '', +}) +class DummyAccountLayoutComponent {} + +@Component({ + selector: 'abp-layout-empty', + template: '', +}) +class DummyEmptyLayoutComponent {} + +const LAYOUTS = [ + DummyApplicationLayoutComponent, + DummyAccountLayoutComponent, + DummyEmptyLayoutComponent, +]; + +@NgModule({ + imports: [RouterModule], + declarations: [...LAYOUTS], +}) +class DummyLayoutModule {} + +@Component({ + selector: 'abp-dummy', + template: '{{route.snapshot.data?.name}} works!', +}) +class DummyComponent { route = inject_1(ActivatedRoute); + +} + +const routes: ABP.Route[] = [ + { + path: '', + name: 'Root', + }, + { + path: '/parentWithLayout', + name: 'ParentWithLayout', + parentName: 'Root', + layout: eLayoutType.application, + }, + { + path: '/parentWithLayout/childWithoutLayout', + name: 'ChildWithoutLayout', + parentName: 'ParentWithLayout', + }, + { + path: '/parentWithLayout/childWithLayout', + name: 'ChildWithLayout', + parentName: 'ParentWithLayout', + layout: eLayoutType.account, + }, + { + path: '/withData', + name: 'WithData', + layout: eLayoutType.application, + }, +]; + +describe('DynamicLayoutComponent', () => { + const createComponent = createRoutingFactory({ + component: RouterOutletComponent, + stubsEnabled: false, + declarations: [DummyComponent, DynamicLayoutComponent], + mocks: [AbpApplicationConfigurationService, HttpClient], + providers: [ + { + provide: RoutesService, + useFactory: () => mockRoutesService(), + }, + ReplaceableComponentsService, + ], + imports: [RouterModule, DummyLayoutModule], + routes: [ + { path: '', component: RouterOutletComponent }, + { + path: 'parentWithLayout', + component: DynamicLayoutComponent, + children: [ + { + path: 'childWithoutLayout', + component: DummyComponent, + data: { name: 'childWithoutLayout' }, + }, + { + path: 'childWithLayout', + component: DummyComponent, + data: { name: 'childWithLayout' }, + }, + ], + }, + { + path: 'withData', + component: DynamicLayoutComponent, + children: [ + { + path: '', + component: DummyComponent, + data: { name: 'withData' }, + }, + ], + data: { layout: eLayoutType.empty }, + }, + { + path: 'withoutLayout', + component: DynamicLayoutComponent, + children: [ + { + path: '', + component: DummyComponent, + data: { name: 'withoutLayout' }, + }, + ], + data: { layout: null }, + }, + ], + }); + + let spectator: SpectatorRouting; + let replaceableComponents: ReplaceableComponentsService; + + beforeEach(async () => { + spectator = createComponent(); + replaceableComponents = spectator.inject(ReplaceableComponentsService); + const routesService = spectator.inject(RoutesService); + routesService.add(routes); + + replaceableComponents.add({ + key: 'Theme.ApplicationLayoutComponent', + component: DummyApplicationLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.AccountLayoutComponent', + component: DummyAccountLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.EmptyLayoutComponent', + component: DummyEmptyLayoutComponent, + }); + }); + + it('should handle application layout from parent abp route and display it', async () => { + spectator.router.navigateByUrl('/parentWithLayout/childWithoutLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-dynamic-layout')).toBeTruthy(); + expect(spectator.query('abp-layout-application')).toBeTruthy(); + }); + + it('should handle account layout from own property and display it', async () => { + spectator.router.navigateByUrl('/parentWithLayout/childWithLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-layout-account')).toBeTruthy(); + }); + + it('should handle empty layout from route data and display it', async () => { + spectator.router.navigateByUrl('/withData'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-layout-empty')).toBeTruthy(); + }); + + it('should display empty layout when layout is null', async () => { + spectator.router.navigateByUrl('/withoutLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-layout-empty')).toBeTruthy(); + }); + + it('should not display any layout when layouts are empty', async () => { + const spy = jest.spyOn(replaceableComponents, 'get'); + spy.mockReturnValue(null); + spectator.detectChanges(); + + spectator.router.navigateByUrl('/withoutLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + + expect(spectator.query('abp-layout-empty')).toBeFalsy(); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 10e59c86f6..94fdf8a527 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -1,178 +1,174 @@ -import { Component, EventEmitter, Inject, Input, Optional, Output } from '@angular/core'; -import { Router } from '@angular/router'; -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; -import { BehaviorSubject } from 'rxjs'; -import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; -import { ReplaceableComponents } from '../models/replaceable-components'; -import { ReplaceableComponentsService } from '../services/replaceable-components.service'; - -@Component({ - selector: 'abp-default-component', - template: '

default

', - exportAs: 'abpDefaultComponent', -}) -class DefaultComponent { - @Input() - oneWay; - - @Input() - twoWay: boolean; - - @Output() - readonly twoWayChange = new EventEmitter(); - - @Output() - readonly someOutput = new EventEmitter(); - - setTwoWay(value) { - this.twoWay = value; - this.twoWayChange.emit(value); - } -} - -@Component({ - selector: 'abp-external-component', - template: '

external

', -}) -class ExternalComponent { - constructor( - @Optional() - @Inject('REPLACEABLE_DATA') - public data: ReplaceableComponents.ReplaceableTemplateData, - ) {} -} - -describe('ReplaceableTemplateDirective', () => { - let spectator: SpectatorDirective; - const get$Res = new BehaviorSubject(undefined); - - const createDirective = createDirectiveFactory({ - directive: ReplaceableTemplateDirective, - declarations: [DefaultComponent, ExternalComponent], - entryComponents: [ExternalComponent], - mocks: [Router], - providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }], - }); - - describe('without external component', () => { - const twoWayChange = jest.fn(a => a); - const someOutput = jest.fn(a => a); - - beforeEach(() => { - spectator = createDirective( - ` -
- -
- `, - { - hostProps: { - oneWay: { label: 'Test' }, - twoWay: false, - twoWayChange, - someOutput, - }, - }, - ); - - const component = spectator.query(DefaultComponent); - spectator.directive.context.initTemplate(component); - spectator.detectChanges(); - }); - - afterEach(() => twoWayChange.mockClear()); - - it('should display the default template when store response is undefined', () => { - expect(spectator.query('abp-default-component')).toBeTruthy(); - }); - - it('should be setted inputs and outputs', () => { - const component = spectator.query(DefaultComponent); - expect(component.oneWay).toEqual({ label: 'Test' }); - expect(component.twoWay).toEqual(false); - }); - - it('should change the component inputs', () => { - const component = spectator.query(DefaultComponent); - spectator.setHostInput({ oneWay: 'test' }); - component.setTwoWay(true); - component.someOutput.emit('someOutput emitted'); - expect(component.oneWay).toBe('test'); - expect(twoWayChange).toHaveBeenCalledWith(true); - expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); - }); - }); - - describe('with external component', () => { - const twoWayChange = jest.fn(a => a); - const someOutput = jest.fn(a => a); - - beforeEach(() => { - spectator = createDirective( - ` -
- -
- `, - { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, - ); - - get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); - }); - - afterEach(() => twoWayChange.mockClear()); - - it('should display the external component', () => { - expect(spectator.query('p')).toHaveText('external'); - }); - - it('should be injected the data object', () => { - const externalComponent = spectator.query(ExternalComponent); - expect(externalComponent.data).toEqual({ - componentKey: 'TestModule.TestComponent', - inputs: { oneWay: { label: 'Test' }, twoWay: false }, - outputs: { someOutput, twoWayChange }, - }); - }); - - it('should be worked all data properties', () => { - const externalComponent = spectator.query(ExternalComponent); - spectator.setHostInput({ oneWay: 'test' }); - externalComponent.data.inputs.twoWay = true; - externalComponent.data.outputs.someOutput('someOutput emitted'); - expect(externalComponent.data.inputs.oneWay).toBe('test'); - expect(twoWayChange).toHaveBeenCalledWith(true); - expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); - - spectator.setHostInput({ twoWay: 'twoWay test' }); - expect(externalComponent.data.inputs.twoWay).toBe('twoWay test'); - }); - - it('should be worked correctly the default component when the external component has been removed from store', () => { - expect(spectator.query('p')).toHaveText('external'); - const externalComponent = spectator.query(ExternalComponent); - spectator.setHostInput({ oneWay: 'test' }); - externalComponent.data.inputs.twoWay = true; - get$Res.next({ component: null, key: 'TestModule.TestComponent' }); - spectator.detectChanges(); - const component = spectator.query(DefaultComponent); - spectator.directive.context.initTemplate(component); - expect(spectator.query('abp-default-component')).toBeTruthy(); - - expect(component.oneWay).toEqual('test'); - expect(component.twoWay).toEqual(true); - }); - - it('should reset default component subscriptions', () => { - get$Res.next({ component: null, key: 'TestModule.TestComponent' }); - const component = spectator.query(DefaultComponent); - spectator.directive.context.initTemplate(component); - spectator.detectChanges(); - const unsubscribe = jest.fn(() => {}); - spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe; - - get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); - expect(unsubscribe).toHaveBeenCalled(); - }); - }); -}); +import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { BehaviorSubject } from 'rxjs'; +import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; +import { ReplaceableComponents } from '../models/replaceable-components'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; + +@Component({ + selector: 'abp-default-component', + template: '

default

', + exportAs: 'abpDefaultComponent', +}) +class DefaultComponent { + @Input() + oneWay; + + @Input() + twoWay: boolean; + + @Output() + readonly twoWayChange = new EventEmitter(); + + @Output() + readonly someOutput = new EventEmitter(); + + setTwoWay(value) { + this.twoWay = value; + this.twoWayChange.emit(value); + } +} + +@Component({ + selector: 'abp-external-component', + template: '

external

', +}) +class ExternalComponent { data = inject>('REPLACEABLE_DATA' as any, { optional: true })!; + +} + +describe('ReplaceableTemplateDirective', () => { + let spectator: SpectatorDirective; + const get$Res = new BehaviorSubject(undefined); + + const createDirective = createDirectiveFactory({ + directive: ReplaceableTemplateDirective, + declarations: [DefaultComponent, ExternalComponent], + entryComponents: [ExternalComponent], + mocks: [Router], + providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }], + }); + + describe('without external component', () => { + const twoWayChange = jest.fn(a => a); + const someOutput = jest.fn(a => a); + + beforeEach(() => { + spectator = createDirective( + ` +
+ +
+ `, + { + hostProps: { + oneWay: { label: 'Test' }, + twoWay: false, + twoWayChange, + someOutput, + }, + }, + ); + + const component = spectator.query(DefaultComponent); + spectator.directive.context.initTemplate(component); + spectator.detectChanges(); + }); + + afterEach(() => twoWayChange.mockClear()); + + it('should display the default template when store response is undefined', () => { + expect(spectator.query('abp-default-component')).toBeTruthy(); + }); + + it('should be setted inputs and outputs', () => { + const component = spectator.query(DefaultComponent); + expect(component.oneWay).toEqual({ label: 'Test' }); + expect(component.twoWay).toEqual(false); + }); + + it('should change the component inputs', () => { + const component = spectator.query(DefaultComponent); + spectator.setHostInput({ oneWay: 'test' }); + component.setTwoWay(true); + component.someOutput.emit('someOutput emitted'); + expect(component.oneWay).toBe('test'); + expect(twoWayChange).toHaveBeenCalledWith(true); + expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); + }); + }); + + describe('with external component', () => { + const twoWayChange = jest.fn(a => a); + const someOutput = jest.fn(a => a); + + beforeEach(() => { + spectator = createDirective( + ` +
+ +
+ `, + { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, + ); + + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + }); + + afterEach(() => twoWayChange.mockClear()); + + it('should display the external component', () => { + expect(spectator.query('p')).toHaveText('external'); + }); + + it('should be injected the data object', () => { + const externalComponent = spectator.query(ExternalComponent); + expect(externalComponent.data).toEqual({ + componentKey: 'TestModule.TestComponent', + inputs: { oneWay: { label: 'Test' }, twoWay: false }, + outputs: { someOutput, twoWayChange }, + }); + }); + + it('should be worked all data properties', () => { + const externalComponent = spectator.query(ExternalComponent); + spectator.setHostInput({ oneWay: 'test' }); + externalComponent.data.inputs.twoWay = true; + externalComponent.data.outputs.someOutput('someOutput emitted'); + expect(externalComponent.data.inputs.oneWay).toBe('test'); + expect(twoWayChange).toHaveBeenCalledWith(true); + expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); + + spectator.setHostInput({ twoWay: 'twoWay test' }); + expect(externalComponent.data.inputs.twoWay).toBe('twoWay test'); + }); + + it('should be worked correctly the default component when the external component has been removed from store', () => { + expect(spectator.query('p')).toHaveText('external'); + const externalComponent = spectator.query(ExternalComponent); + spectator.setHostInput({ oneWay: 'test' }); + externalComponent.data.inputs.twoWay = true; + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); + spectator.detectChanges(); + const component = spectator.query(DefaultComponent); + spectator.directive.context.initTemplate(component); + expect(spectator.query('abp-default-component')).toBeTruthy(); + + expect(component.oneWay).toEqual('test'); + expect(component.twoWay).toEqual(true); + }); + + it('should reset default component subscriptions', () => { + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); + const component = spectator.query(DefaultComponent); + spectator.directive.context.initTemplate(component); + spectator.detectChanges(); + const unsubscribe = jest.fn(() => {}); + spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe; + + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + expect(unsubscribe).toHaveBeenCalled(); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts b/npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts index b4d180b17a..e7f03cdf48 100644 --- a/npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts +++ b/npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts @@ -1,12 +1,17 @@ import { ConfigStateService, PermissionService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class MockPermissionService extends PermissionService { - constructor(protected configState: ConfigStateService) { - super(configState); + protected configState: ConfigStateService; + + constructor() { + const configState = inject(ConfigStateService); + super(); + this.configState = configState; + this.grantAllPolicies(); } diff --git a/npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts b/npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts index 7fd171c1d4..57d326ac4f 100644 --- a/npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts +++ b/npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts @@ -3,24 +3,33 @@ import { CORE_OPTIONS, EnvironmentService, ExternalHttpClient, - HttpErrorReporterService, RestService, } from '@abp/ng.core'; import { HttpClient } from '@angular/common/http'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { Observable, throwError } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class MockRestService extends RestService { - constructor( - @Inject(CORE_OPTIONS) protected options: ABP.Root, - protected http: HttpClient, - protected externalhttp: ExternalHttpClient, - protected environment: EnvironmentService, - ) { - super(options, http,externalhttp, environment, null as unknown as HttpErrorReporterService); + protected options: ABP.Root; + protected http: HttpClient; + protected externalhttp: ExternalHttpClient; + protected environment: EnvironmentService; + + constructor() { + const options = inject(CORE_OPTIONS); + const http = inject(HttpClient); + const externalhttp = inject(ExternalHttpClient); + const environment = inject(EnvironmentService); + + super(); + + this.options = options; + this.http = http; + this.externalhttp = externalhttp; + this.environment = environment; } handleError(err: any): Observable { diff --git a/npm/ng-packs/packages/feature-management/proxy/src/lib/proxy/feature-management/features.service.ts b/npm/ng-packs/packages/feature-management/proxy/src/lib/proxy/feature-management/features.service.ts index 189076428d..67616fd4d1 100644 --- a/npm/ng-packs/packages/feature-management/proxy/src/lib/proxy/feature-management/features.service.ts +++ b/npm/ng-packs/packages/feature-management/proxy/src/lib/proxy/feature-management/features.service.ts @@ -1,11 +1,13 @@ import type { GetFeatureListResultDto, UpdateFeaturesDto } from './models'; import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class FeaturesService { + private restService = inject(RestService); + apiName = 'AbpFeatureManagement'; delete = (providerName: string, providerKey: string) => @@ -38,6 +40,4 @@ export class FeaturesService { }, { apiName: this.apiName }, ); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-role.service.ts b/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-role.service.ts index 6e296c171b..c1aabf7091 100644 --- a/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-role.service.ts +++ b/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-role.service.ts @@ -1,12 +1,14 @@ import type { GetIdentityRolesInput, IdentityRoleCreateDto, IdentityRoleDto, IdentityRoleUpdateDto } from './models'; import { RestService } from '@abp/ng.core'; import type { ListResultDto, PagedResultDto } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class IdentityRoleService { + private restService = inject(RestService); + apiName = 'AbpIdentity'; create = (input: IdentityRoleCreateDto) => @@ -53,6 +55,4 @@ export class IdentityRoleService { body: input, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user-lookup.service.ts b/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user-lookup.service.ts index af4583aa28..cf8d01e81f 100644 --- a/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user-lookup.service.ts +++ b/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user-lookup.service.ts @@ -1,13 +1,15 @@ import type { UserLookupCountInputDto, UserLookupSearchInputDto } from './models'; import { RestService } from '@abp/ng.core'; import type { ListResultDto } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import type { UserData } from '../users/models'; @Injectable({ providedIn: 'root', }) export class IdentityUserLookupService { + private restService = inject(RestService); + apiName = 'AbpIdentity'; findById = (id: string) => @@ -39,6 +41,4 @@ export class IdentityUserLookupService { params: { filter: input.filter, sorting: input.sorting, skipCount: input.skipCount, maxResultCount: input.maxResultCount }, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user.service.ts b/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user.service.ts index c3b6f203f2..a538a07feb 100644 --- a/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user.service.ts +++ b/npm/ng-packs/packages/identity/proxy/src/lib/proxy/identity/identity-user.service.ts @@ -1,12 +1,14 @@ import type { GetIdentityUsersInput, IdentityRoleDto, IdentityUserCreateDto, IdentityUserDto, IdentityUserUpdateDto, IdentityUserUpdateRolesDto } from './models'; import { RestService } from '@abp/ng.core'; import type { ListResultDto, PagedResultDto } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class IdentityUserService { + private restService = inject(RestService); + apiName = 'AbpIdentity'; create = (input: IdentityUserCreateDto) => @@ -84,6 +86,4 @@ export class IdentityUserService { body: input, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/oauth/src/lib/handlers/oauth-configuration.handler.ts b/npm/ng-packs/packages/oauth/src/lib/handlers/oauth-configuration.handler.ts index a2a9228b78..42ddb89c90 100644 --- a/npm/ng-packs/packages/oauth/src/lib/handlers/oauth-configuration.handler.ts +++ b/npm/ng-packs/packages/oauth/src/lib/handlers/oauth-configuration.handler.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { AuthConfig, OAuthService } from "angular-oauth2-oidc"; import compare from 'just-compare'; import { filter, map } from 'rxjs/operators'; @@ -8,11 +8,11 @@ import { ABP, EnvironmentService, CORE_OPTIONS } from '@abp/ng.core'; providedIn: 'root', }) export class OAuthConfigurationHandler { - constructor( - private oAuthService: OAuthService, - private environmentService: EnvironmentService, - @Inject(CORE_OPTIONS) private options: ABP.Root, - ) { + private oAuthService = inject(OAuthService); + private environmentService = inject(EnvironmentService); + private options = inject(CORE_OPTIONS); + + constructor() { this.listenToSetEnvironment(); } diff --git a/npm/ng-packs/packages/oauth/src/lib/interceptors/api.interceptor.ts b/npm/ng-packs/packages/oauth/src/lib/interceptors/api.interceptor.ts index 07fad0428a..c2039210f4 100644 --- a/npm/ng-packs/packages/oauth/src/lib/interceptors/api.interceptor.ts +++ b/npm/ng-packs/packages/oauth/src/lib/interceptors/api.interceptor.ts @@ -1,5 +1,5 @@ import { HttpEvent, HttpHandler, HttpHeaders, HttpRequest } from '@angular/common/http'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { finalize } from 'rxjs/operators'; import { Observable } from 'rxjs'; @@ -15,12 +15,10 @@ import { providedIn: 'root', }) export class OAuthApiInterceptor implements IApiInterceptor { - constructor( - private oAuthService: OAuthService, - private sessionState: SessionStateService, - private httpWaitService: HttpWaitService, - @Inject(TENANT_KEY) private tenantKey: string, - ) {} + private oAuthService = inject(OAuthService); + private sessionState = inject(SessionStateService); + private httpWaitService = inject(HttpWaitService); + private tenantKey = inject(TENANT_KEY); intercept(request: HttpRequest, next: HttpHandler): Observable> { this.httpWaitService.addRequest(request); diff --git a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts index a490f92dd2..51c97a8101 100644 --- a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts +++ b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { Params } from '@angular/router'; import { from, Observable, lastValueFrom, EMPTY } from 'rxjs'; import { filter, map, switchMap, take, tap } from 'rxjs/operators'; @@ -13,6 +13,8 @@ import { HttpHeaders } from '@angular/common/http'; providedIn: 'root', }) export class AbpOAuthService implements IAuthService { + protected injector = inject(Injector); + private strategy!: AuthFlowStrategy; private readonly oAuthService: OAuthService; @@ -28,7 +30,7 @@ export class AbpOAuthService implements IAuthService { return this.strategy.isInternalAuth; } - constructor(protected injector: Injector) { + constructor() { this.oAuthService = this.injector.get(OAuthService); } diff --git a/npm/ng-packs/packages/permission-management/proxy/src/lib/proxy/permissions.service.ts b/npm/ng-packs/packages/permission-management/proxy/src/lib/proxy/permissions.service.ts index 45ac0b40e1..dbd13ca778 100644 --- a/npm/ng-packs/packages/permission-management/proxy/src/lib/proxy/permissions.service.ts +++ b/npm/ng-packs/packages/permission-management/proxy/src/lib/proxy/permissions.service.ts @@ -1,11 +1,13 @@ import type { GetPermissionListResultDto, UpdatePermissionsDto } from './models'; import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class PermissionsService { + private restService = inject(RestService); + apiName = 'AbpPermissionManagement'; get = (providerName: string, providerKey: string) => @@ -24,6 +26,4 @@ export class PermissionsService { body: input, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template b/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template index aae3b43471..8b0c343173 100644 --- a/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template +++ b/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template @@ -5,6 +5,7 @@ providedIn: 'root', }) export class <%= name %>Service { + private restService = inject(RestService); apiName = '<%= apiName %>';<% for (let {body, signature} of methods) { %> <% @@ -26,6 +27,4 @@ export class <%= name %>Service { body: <%= body.body %>,<% } %> }, { apiName: this.apiName,...config });<% } %> - - constructor(private restService: RestService) {} -} +} \ No newline at end of file diff --git a/npm/ng-packs/packages/schematics/src/utils/service.ts b/npm/ng-packs/packages/schematics/src/utils/service.ts index 8f48ed3693..1280aa3aef 100644 --- a/npm/ng-packs/packages/schematics/src/utils/service.ts +++ b/npm/ng-packs/packages/schematics/src/utils/service.ts @@ -46,7 +46,7 @@ export function createControllerToServiceMapper({ [], ); imports.push(new Import({ path: '@abp/ng.core', specifiers: ['RestService', 'Rest'] })); - imports.push(new Import({ path: '@angular/core', specifiers: ['Injectable'] })); + imports.push(new Import({ path: '@angular/core', specifiers: ['Injectable', 'inject'] })); sortImports(imports); const methods = actions.map(mapActionToMethod); sortMethods(methods); diff --git a/npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.ts b/npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.ts index f61c158e0e..8745e0ffd4 100644 --- a/npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.ts +++ b/npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.ts @@ -45,6 +45,10 @@ const { required, email } = Validators; ], }) export class EmailSettingGroupComponent implements OnInit { + private emailSettingsService = inject(EmailSettingsService); + private fb = inject(UntypedFormBuilder); + private toasterService = inject(ToasterService); + protected readonly localizationService = inject(LocalizationService); protected readonly configStateSevice = inject(ConfigStateService); protected readonly currentUserEmail = toSignal( @@ -58,12 +62,6 @@ export class EmailSettingGroupComponent implements OnInit { isEmailTestModalOpen = false; modalSize: NgbModalOptions = { size: 'lg' }; - constructor( - private emailSettingsService: EmailSettingsService, - private fb: UntypedFormBuilder, - private toasterService: ToasterService, - ) {} - ngOnInit() { this.getData(); } diff --git a/npm/ng-packs/packages/setting-management/config/src/lib/proxy/email-settings.service.ts b/npm/ng-packs/packages/setting-management/config/src/lib/proxy/email-settings.service.ts index 0252b18c12..a1835e1a38 100644 --- a/npm/ng-packs/packages/setting-management/config/src/lib/proxy/email-settings.service.ts +++ b/npm/ng-packs/packages/setting-management/config/src/lib/proxy/email-settings.service.ts @@ -1,6 +1,6 @@ import type { EmailSettingsDto, SendTestEmailInput, UpdateEmailSettingsDto } from './models'; import { RestService } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; /** @deprecated This method is deprecated, use it from @abp/ng.setting-management/proxy @@ -9,6 +9,8 @@ import { Injectable } from '@angular/core'; providedIn: 'root', }) export class EmailSettingsService { + private restService = inject(RestService); + apiName = 'SettingManagement'; get = () => @@ -33,6 +35,4 @@ export class EmailSettingsService { body: input, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/email-settings.service.ts b/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/email-settings.service.ts index a05cfc27a7..af6a2f5843 100644 --- a/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/email-settings.service.ts +++ b/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/email-settings.service.ts @@ -1,11 +1,13 @@ import type { EmailSettingsDto, SendTestEmailInput, UpdateEmailSettingsDto } from './models'; import { RestService, Rest } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) -export class EmailSettingsService { +export class EmailSettingsService { + private restService = inject(RestService); + apiName = 'SettingManagement'; @@ -33,6 +35,4 @@ export class EmailSettingsService { body: input, }, { apiName: this.apiName,...config }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/time-zone-settings.service.ts b/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/time-zone-settings.service.ts index 7141bce27d..1a20916673 100644 --- a/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/time-zone-settings.service.ts +++ b/npm/ng-packs/packages/setting-management/proxy/src/lib/proxy/time-zone-settings.service.ts @@ -1,11 +1,13 @@ import type { NameValue } from './volo/abp/models'; import { RestService, Rest } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) -export class TimeZoneSettingsService { +export class TimeZoneSettingsService { + private restService = inject(RestService); + apiName = 'SettingManagement'; @@ -33,6 +35,4 @@ export class TimeZoneSettingsService { params: { timezone }, }, { apiName: this.apiName,...config }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts index 61d1667be4..28174f8ad8 100644 --- a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts +++ b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts @@ -1,12 +1,14 @@ import type { GetTenantsInput, TenantCreateDto, TenantDto, TenantUpdateDto } from './models'; import { RestService } from '@abp/ng.core'; import type { PagedResultDto } from '@abp/ng.core'; -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class TenantService { + private restService = inject(RestService); + apiName = 'AbpTenantManagement'; create = (input: TenantCreateDto) => @@ -69,6 +71,4 @@ export class TenantService { params: { defaultConnectionString }, }, { apiName: this.apiName }); - - constructor(private restService: RestService) {} } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/account-layout.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/account-layout.component.ts index a6e1b6ad91..96c3be352c 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/account-layout.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/account-layout.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component } from '@angular/core'; +import { AfterViewInit, Component, inject } from '@angular/core'; import { eLayoutType, ReplaceableTemplateDirective, SubscriptionService } from '@abp/ng.core'; import { LayoutService } from '../../services/layout.service'; import { CommonModule } from '@angular/common'; @@ -27,13 +27,13 @@ import { collapseWithMargin } from '@abp/ng.theme.shared'; ], }) export class AccountLayoutComponent implements AfterViewInit { + service = inject(LayoutService); + // required for dynamic component static type = eLayoutType.account; authWrapperKey = 'Account.AuthWrapperComponent'; - constructor(public service: LayoutService) {} - ngAfterViewInit() { this.service.subscribeWindowSize(); } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/auth-wrapper/auth-wrapper.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/auth-wrapper/auth-wrapper.component.ts index 5094551ed2..3c2db0a33b 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/auth-wrapper/auth-wrapper.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/auth-wrapper/auth-wrapper.component.ts @@ -1,15 +1,15 @@ -import { AuthWrapperService } from '@abp/ng.account.core'; -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { LocalizationPipe, ReplaceableTemplateDirective } from '@abp/ng.core'; -import { TenantBoxComponent } from '../tenant-box/tenant-box.component'; +import { AuthWrapperService } from '@abp/ng.account.core'; +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { LocalizationPipe, ReplaceableTemplateDirective } from '@abp/ng.core'; +import { TenantBoxComponent } from '../tenant-box/tenant-box.component'; + +@Component({ + selector: 'abp-auth-wrapper', + templateUrl: './auth-wrapper.component.html', + providers: [AuthWrapperService], + imports: [CommonModule, TenantBoxComponent, ReplaceableTemplateDirective, LocalizationPipe], +}) +export class AuthWrapperComponent { service = inject(AuthWrapperService); -@Component({ - selector: 'abp-auth-wrapper', - templateUrl: './auth-wrapper.component.html', - providers: [AuthWrapperService], - imports: [CommonModule, TenantBoxComponent, ReplaceableTemplateDirective, LocalizationPipe], -}) -export class AuthWrapperComponent { - constructor(public service: AuthWrapperService) {} -} +} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.ts index 4aadd1effb..af4807d2bb 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.ts @@ -1,23 +1,23 @@ -import { TenantBoxService } from '@abp/ng.account.core'; -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { LocalizationPipe } from '@abp/ng.core'; -import { ButtonComponent, ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared'; -import { FormsModule } from '@angular/forms'; +import { TenantBoxService } from '@abp/ng.account.core'; +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { LocalizationPipe } from '@abp/ng.core'; +import { ButtonComponent, ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'abp-tenant-box', + templateUrl: './tenant-box.component.html', + providers: [TenantBoxService], + imports: [ + CommonModule, + FormsModule, + ModalComponent, + LocalizationPipe, + ButtonComponent, + ModalCloseDirective, + ], +}) +export class TenantBoxComponent { service = inject(TenantBoxService); -@Component({ - selector: 'abp-tenant-box', - templateUrl: './tenant-box.component.html', - providers: [TenantBoxService], - imports: [ - CommonModule, - FormsModule, - ModalComponent, - LocalizationPipe, - ButtonComponent, - ModalCloseDirective, - ], -}) -export class TenantBoxComponent { - constructor(public service: TenantBoxService) {} -} +} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts index fcaeacdf1a..b3047d72ed 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/logo/logo.component.ts @@ -1,5 +1,5 @@ import { ApplicationInfo, EnvironmentService } from '@abp/ng.core'; -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ @@ -16,9 +16,9 @@ import { CommonModule } from '@angular/common'; imports: [CommonModule], }) export class LogoComponent { + private environment = inject(EnvironmentService); + get appInfo(): ApplicationInfo { return this.environment.getEnvironment().application; } - - constructor(private environment: EnvironmentService) {} } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts index 40f2d4a342..de9ef1f3f0 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/current-user.component.ts @@ -9,7 +9,7 @@ import { ToInjectorPipe, } from '@abp/ng.core'; import { AbpVisibleDirective, UserMenu, UserMenuService } from '@abp/ng.theme.shared'; -import { Component, Inject, TrackByFunction } from '@angular/core'; +import { Component, TrackByFunction, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { CommonModule } from '@angular/common'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @@ -27,6 +27,12 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; ], }) export class CurrentUserComponent { + readonly navigateToManageProfile = inject(NAVIGATE_TO_MANAGE_PROFILE); + readonly userMenu = inject(UserMenuService); + private authService = inject(AuthService); + private configState = inject(ConfigStateService); + private sessionState = inject(SessionStateService); + currentUser$: Observable = this.configState.getOne$('currentUser'); selectedTenant$ = this.sessionState.getTenant$(); @@ -36,14 +42,6 @@ export class CurrentUserComponent { return window.innerWidth < 992; } - constructor( - @Inject(NAVIGATE_TO_MANAGE_PROFILE) public readonly navigateToManageProfile: () => void, - public readonly userMenu: UserMenuService, - private authService: AuthService, - private configState: ConfigStateService, - private sessionState: SessionStateService, - ) {} - navigateToLogin() { this.authService.navigateToLogin(); } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts index 67d09d300b..0a7b3e8190 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/languages.component.ts @@ -1,5 +1,5 @@ import { ConfigStateService, LanguageInfo, SessionStateService } from '@abp/ng.core'; -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; @@ -42,6 +42,9 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; imports: [CommonModule, NgbDropdownModule], }) export class LanguagesComponent { + private sessionState = inject(SessionStateService); + private configState = inject(ConfigStateService); + get smallScreen(): boolean { return window.innerWidth < 992; } @@ -69,11 +72,6 @@ export class LanguagesComponent { return this.sessionState.getLanguage(); } - constructor( - private sessionState: SessionStateService, - private configState: ConfigStateService, - ) {} - onChangeLang(cultureName: string) { this.sessionState.setLanguage(cultureName); } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts index d2e4a516b4..15b359f929 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/nav-items/nav-items.component.ts @@ -1,5 +1,5 @@ import { AbpVisibleDirective, NavItem, NavItemsService } from '@abp/ng.theme.shared'; -import { Component, TrackByFunction } from '@angular/core'; +import { Component, TrackByFunction, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PermissionDirective, ToInjectorPipe } from '@abp/ng.core'; @@ -9,7 +9,7 @@ import { PermissionDirective, ToInjectorPipe } from '@abp/ng.core'; imports: [CommonModule, AbpVisibleDirective, PermissionDirective, ToInjectorPipe], }) export class NavItemsComponent { - trackByFn: TrackByFunction = (_, element) => element.id; + readonly navItems = inject(NavItemsService); - constructor(public readonly navItems: NavItemsService) {} + trackByFn: TrackByFunction = (_, element) => element.id; } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/page-alert-container/page-alert-container.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/page-alert-container/page-alert-container.component.ts index b28cd6c81b..9347850b53 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/page-alert-container/page-alert-container.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/page-alert-container/page-alert-container.component.ts @@ -1,14 +1,14 @@ -import { Component, ViewEncapsulation } from '@angular/core'; -import { PageAlertService } from '@abp/ng.theme.shared'; -import { CommonModule } from '@angular/common'; -import { LocalizationPipe, SafeHtmlPipe } from '@abp/ng.core'; +import { Component, ViewEncapsulation, inject } from '@angular/core'; +import { PageAlertService } from '@abp/ng.theme.shared'; +import { CommonModule } from '@angular/common'; +import { LocalizationPipe, SafeHtmlPipe } from '@abp/ng.core'; + +@Component({ + selector: 'abp-page-alert-container', + templateUrl: './page-alert-container.component.html', + encapsulation: ViewEncapsulation.None, + imports: [CommonModule, LocalizationPipe, SafeHtmlPipe], +}) +export class PageAlertContainerComponent { service = inject(PageAlertService); -@Component({ - selector: 'abp-page-alert-container', - templateUrl: './page-alert-container.component.html', - encapsulation: ViewEncapsulation.None, - imports: [CommonModule, LocalizationPipe, SafeHtmlPipe], -}) -export class PageAlertContainerComponent { - constructor(public service: PageAlertService) {} -} +} diff --git a/npm/ng-packs/packages/theme-basic/src/lib/handlers/lazy-style.handler.ts b/npm/ng-packs/packages/theme-basic/src/lib/handlers/lazy-style.handler.ts index 02d9f69817..1064a143fb 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/handlers/lazy-style.handler.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/handlers/lazy-style.handler.ts @@ -1,6 +1,6 @@ import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; import { DocumentDirHandlerService, LocaleDirection } from '@abp/ng.theme.shared'; -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { LAZY_STYLES } from '../tokens/lazy-styles.token'; export const BOOTSTRAP = 'bootstrap-{{dir}}.min.css'; @@ -23,7 +23,9 @@ export class LazyStyleHandler { return this._dir; } - constructor(injector: Injector) { + constructor() { + const injector = inject(Injector); + this.setStyles(injector); this.setLazyLoad(injector); this.listenToDirectionChanges(injector); @@ -88,8 +90,8 @@ export function createLazyStyleHref(style: string, dir: string): string { return style.replace(/{{\s*dir\s*}}/g, dir); } -export function initLazyStyleHandler(injector: Injector) { - return () => new LazyStyleHandler(injector); +export function initLazyStyleHandler() { + return () => new LazyStyleHandler(); } interface LoadedStyle { diff --git a/npm/ng-packs/packages/theme-basic/src/lib/services/layout.service.ts b/npm/ng-packs/packages/theme-basic/src/lib/services/layout.service.ts index a990173218..0f8a535fe5 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/services/layout.service.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/services/layout.service.ts @@ -1,11 +1,14 @@ import {RouterEvents, SubscriptionService} from '@abp/ng.core'; -import { ChangeDetectorRef, Injectable } from '@angular/core'; +import { ChangeDetectorRef, Injectable, inject } from '@angular/core'; import { fromEvent } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { eThemeBasicComponents } from '../enums'; @Injectable() export class LayoutService { + private subscription = inject(SubscriptionService); + private cdRef = inject(ChangeDetectorRef); + isCollapsed = true; smallScreen!: boolean; // do not set true or false @@ -16,9 +19,10 @@ export class LayoutService { navItemsComponentKey = eThemeBasicComponents.NavItems; - constructor(private subscription: SubscriptionService, - private cdRef: ChangeDetectorRef, - routerEvents:RouterEvents) { + constructor() { + const subscription = this.subscription; + const routerEvents = inject(RouterEvents); + subscription.addOne(routerEvents.getNavigationEvents("End"),() => { this.isCollapsed = true; }) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts index 4c7bd3cf30..f269be8d1e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts @@ -6,7 +6,7 @@ import { SubscriptionService, TreeNode, } from '@abp/ng.core'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, inject } from '@angular/core'; import { Router } from '@angular/router'; import { map, startWith } from 'rxjs/operators'; import { eThemeSharedRouteNames } from '../../enums/route-names'; @@ -20,15 +20,13 @@ import { BreadcrumbItemsComponent } from '../breadcrumb-items/breadcrumb-items.c imports: [BreadcrumbItemsComponent], }) export class BreadcrumbComponent implements OnInit { - segments: Partial[] = []; + readonly cdRef = inject(ChangeDetectorRef); + private router = inject(Router); + private routes = inject(RoutesService); + private subscription = inject(SubscriptionService); + private routerEvents = inject(RouterEvents); - constructor( - public readonly cdRef: ChangeDetectorRef, - private router: Router, - private routes: RoutesService, - private subscription: SubscriptionService, - private routerEvents: RouterEvents, - ) {} + segments: Partial[] = []; ngOnInit(): void { this.subscription.addOne( diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts index fdebb542a7..4c89b9ac10 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts @@ -1,13 +1,14 @@ /* eslint-disable @angular-eslint/no-output-native */ -import { - Component, - ElementRef, - EventEmitter, - Input, - OnInit, - Output, - Renderer2, - ViewChild, +import { + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + Renderer2, + ViewChild, + inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ABP } from '@abp/ng.core'; @@ -32,6 +33,8 @@ import { ABP } from '@abp/ng.core'; imports: [CommonModule], }) export class ButtonComponent implements OnInit { + private renderer = inject(Renderer2); + @Input() buttonId = ''; @@ -75,8 +78,6 @@ export class ButtonComponent implements OnInit { return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass || 'd-none'}`; } - constructor(private renderer: Renderer2) {} - ngOnInit() { if (this.attributes) { Object.keys(this.attributes).forEach(key => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts index f211b758e3..dcf7d4c952 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/confirmation/confirmation.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ReplaySubject } from 'rxjs'; import { Confirmation } from '../../models/confirmation'; @@ -12,7 +12,8 @@ import { LocalizationPipe } from '@abp/ng.core'; imports: [CommonModule, LocalizationPipe], }) export class ConfirmationComponent { - constructor(@Inject(CONFIRMATION_ICONS) private icons: ConfirmationIcons) {} + private icons = inject(CONFIRMATION_ICONS); + confirm = Confirmation.Status.confirm; reject = Confirmation.Status.reject; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts index 501fbbb82a..d611bc5f3d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts @@ -1,5 +1,4 @@ -import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { combineLatest, Subscription, timer } from 'rxjs'; import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core'; @@ -24,6 +23,11 @@ import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng imports: [CommonModule], }) export class LoaderBarComponent implements OnDestroy, OnInit { + private cdRef = inject(ChangeDetectorRef); + private subscription = inject(SubscriptionService); + private httpWaitService = inject(HttpWaitService); + private routerWaitService = inject(RouterWaitService); + protected _isLoading!: boolean; @Input() @@ -73,14 +77,6 @@ export class LoaderBarComponent implements OnDestroy, OnInit { return `0 0 10px rgba(${this.color}, 0.5)`; } - constructor( - private router: Router, - private cdRef: ChangeDetectorRef, - private subscription: SubscriptionService, - private httpWaitService: HttpWaitService, - private routerWaitService: RouterWaitService, - ) {} - ngOnInit() { this.subscribeLoading(); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-close.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-close.directive.ts index 96455d1151..e15422d31b 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-close.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal-close.directive.ts @@ -1,11 +1,15 @@ -import { Directive, HostListener, Optional } from '@angular/core'; +import { Directive, HostListener, inject } from '@angular/core'; import { ModalComponent } from './modal.component'; @Directive({ selector: '[abpClose]', }) export class ModalCloseDirective { - constructor(@Optional() private modal: ModalComponent) { + private modal = inject(ModalComponent, { optional: true })!; + + constructor() { + const modal = this.modal; + if (!modal) { console.error('Please use abpClose within an abp-modal'); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/disabled.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/disabled.directive.ts index c0a1a74995..e00d827ba7 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/disabled.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/disabled.directive.ts @@ -1,15 +1,15 @@ -import { Directive, Host, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Directive, Input, OnChanges, SimpleChanges, inject } from '@angular/core'; import { NgControl } from '@angular/forms'; @Directive({ selector: '[abpDisabled]', }) export class DisabledDirective implements OnChanges { + private ngControl = inject(NgControl, { host: true }); + @Input() abpDisabled = false; - constructor(@Host() private ngControl: NgControl) {} - // Related issue: https://github.com/angular/angular/issues/35330 ngOnChanges({ abpDisabled }: SimpleChanges) { if (this.ngControl.control && abpDisabled) { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts index 94d963ce2e..b26be01a34 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts @@ -1,17 +1,20 @@ -import { - AfterViewInit, - ChangeDetectorRef, - Directive, - ElementRef, - HostBinding, - Input, - NgModule, +import { + AfterViewInit, + ChangeDetectorRef, + Directive, + ElementRef, + HostBinding, + Input, + inject } from '@angular/core'; @Directive({ selector: '[abpEllipsis]', }) export class EllipsisDirective implements AfterViewInit { + private cdRef = inject(ChangeDetectorRef); + private elRef = inject(ElementRef); + @Input('abpEllipsis') width?: string; @@ -37,11 +40,6 @@ export class EllipsisDirective implements AfterViewInit { return this.enabled && this.width ? this.width || '170px' : undefined; } - constructor( - private cdRef: ChangeDetectorRef, - private elRef: ElementRef, - ) {} - ngAfterViewInit() { this.title = this.title || (this.elRef.nativeElement as HTMLElement).innerText; this.cdRef.detectChanges(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts index 9de000e15f..c117262583 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts @@ -1,16 +1,16 @@ -import { - ComponentFactoryResolver, - ComponentRef, - Directive, - ElementRef, - EmbeddedViewRef, - HostBinding, - Injector, - Input, - OnDestroy, - OnInit, - Renderer2, - ViewContainerRef, +import { + ComponentFactoryResolver, + ComponentRef, + Directive, + ElementRef, + EmbeddedViewRef, + HostBinding, + Injector, + Input, + OnDestroy, + OnInit, + Renderer2, + inject } from '@angular/core'; import { Subscription, timer } from 'rxjs'; import { take } from 'rxjs/operators'; @@ -20,6 +20,11 @@ import { LoadingComponent } from '../components'; selector: '[abpLoading]', }) export class LoadingDirective implements OnInit, OnDestroy { + private elRef = inject>(ElementRef); + private cdRes = inject(ComponentFactoryResolver); + private injector = inject(Injector); + private renderer = inject(Renderer2); + private _loading!: boolean; @HostBinding('style.position') @@ -77,14 +82,6 @@ export class LoadingDirective implements OnInit, OnDestroy { rootNode: HTMLDivElement | null = null; timerSubscription: Subscription | null = null; - constructor( - private elRef: ElementRef, - private vcRef: ViewContainerRef, - private cdRes: ComponentFactoryResolver, - private injector: Injector, - private renderer: Renderer2, - ) {} - ngOnInit() { if (!this.targetElement) { const { offsetHeight, offsetWidth } = this.elRef.nativeElement; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts index c7dc96f3c9..23ae8736f8 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts @@ -1,5 +1,5 @@ import { DOCUMENT } from '@angular/common'; -import { AfterViewInit, Directive, HostBinding, Inject, Input, OnDestroy } from '@angular/core'; +import { AfterViewInit, Directive, HostBinding, Input, OnDestroy, inject } from '@angular/core'; import { ColumnMode, DatatableComponent, ScrollerComponent } from '@swimlane/ngx-datatable'; import { fromEvent, Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; @@ -10,6 +10,9 @@ import { debounceTime } from 'rxjs/operators'; exportAs: 'ngxDatatableDefault', }) export class NgxDatatableDefaultDirective implements AfterViewInit, OnDestroy { + private table = inject(DatatableComponent); + private document = inject(DOCUMENT); + private subscription = new Subscription(); private resizeDiff = 0; @@ -20,10 +23,7 @@ export class NgxDatatableDefaultDirective implements AfterViewInit, OnDestroy { return `ngx-datatable ${this.class}`; } - constructor( - private table: DatatableComponent, - @Inject(DOCUMENT) private document: MockDocument, - ) { + constructor() { this.table.columnMode = ColumnMode.force; this.table.footerHeight = 50; this.table.headerHeight = 50; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/directives/visible.directive.ts b/npm/ng-packs/packages/theme-shared/src/lib/directives/visible.directive.ts index 9613779472..7532041462 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/directives/visible.directive.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/directives/visible.directive.ts @@ -1,10 +1,13 @@ -import { OnInit, Directive, OnDestroy, Input, ViewContainerRef, TemplateRef } from '@angular/core'; +import { OnInit, Directive, OnDestroy, Input, ViewContainerRef, TemplateRef, inject } from '@angular/core'; import { EMPTY, from, Observable, of, Subscription } from 'rxjs'; @Directive({ selector: '[abpVisible]', }) export class AbpVisibleDirective implements OnDestroy, OnInit { + private viewContainerRef = inject(ViewContainerRef); + private templateRef = inject>(TemplateRef); + conditionSubscription: Subscription | undefined; isVisible: boolean | undefined; @@ -16,11 +19,6 @@ export class AbpVisibleDirective implements OnDestroy, OnInit { } private condition$: Observable = of(false); - - constructor( - private viewContainerRef: ViewContainerRef, - private templateRef: TemplateRef, - ) {} ngOnInit(): void { this.updateVisibility(); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/document-dir.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/document-dir.handler.ts index 8260a39c27..7ca9426076 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/document-dir.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/document-dir.handler.ts @@ -1,14 +1,16 @@ import { getLocaleDirection, LocalizationService } from '@abp/ng.core'; -import { Injectable, Injector } from '@angular/core'; +import { Injectable, Injector, inject } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; import { LocaleDirection } from '../models/common'; @Injectable() export class DocumentDirHandlerService { + protected injector = inject(Injector); + private dir = new BehaviorSubject('ltr'); dir$ = this.dir.asObservable(); - constructor(protected injector: Injector) { + constructor() { this.listenToLanguageChanges(); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 07c5721625..f4a799ba1b 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -17,6 +17,8 @@ import { RouterErrorHandlerService } from '../services/router-error-handler.serv @Injectable({ providedIn: 'root' }) export class ErrorHandler { + protected injector = inject(Injector); + protected readonly httpErrorReporter = inject(HttpErrorReporterService); protected readonly confirmationService = inject(ConfirmationService); protected readonly routerErrorHandlerService = inject(RouterErrorHandlerService); @@ -24,7 +26,7 @@ export class ErrorHandler { protected readonly customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); protected readonly httpErrorHandler = inject(HTTP_ERROR_HANDLER, { optional: true }); - constructor(protected injector: Injector) { + constructor() { this.listenToRestError(); this.listenToRouterError(); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts index 5eeadb0ab6..ead1f0c91a 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/confirmation.service.ts @@ -1,5 +1,5 @@ import { ContentProjectionService, LocalizationParam, PROJECTION_STRATEGY } from '@abp/ng.core'; -import { ComponentRef, Injectable } from '@angular/core'; +import { ComponentRef, Injectable, inject } from '@angular/core'; import { fromEvent, Observable, ReplaySubject, Subject } from 'rxjs'; import { debounceTime, filter, takeUntil } from 'rxjs/operators'; import { ConfirmationComponent } from '../components/confirmation/confirmation.component'; @@ -7,6 +7,8 @@ import { Confirmation } from '../models/confirmation'; @Injectable({ providedIn: 'root' }) export class ConfirmationService { + private contentProjectionService = inject(ContentProjectionService); + status$!: Subject; confirmation$ = new ReplaySubject(1); @@ -17,8 +19,6 @@ export class ConfirmationService { this.status$.next(status); }; - constructor(private contentProjectionService: ContentProjectionService) {} - private setContainer() { this.containerComponentRef = this.contentProjectionService.projectContent( PROJECTION_STRATEGY.AppendComponentToBody(ConfirmationComponent, { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts b/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts index 4a85a4dab6..40a99d98da 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/utils/date-parser-formatter.ts @@ -1,6 +1,6 @@ import { ApplicationLocalizationConfigurationDto, ConfigStateService } from '@abp/ng.core'; import { formatDate } from '@angular/common'; -import { Inject, Injectable, LOCALE_ID } from '@angular/core'; +import { Injectable, LOCALE_ID, inject } from '@angular/core'; import { NgbDateParserFormatter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; function isNumber(value: any): boolean { @@ -13,10 +13,14 @@ function toInteger(value: any): number { @Injectable() export class DateParserFormatter extends NgbDateParserFormatter { - constructor(private configState: ConfigStateService, @Inject(LOCALE_ID) private locale: string) { + private configState = inject(ConfigStateService); + private locale = inject(LOCALE_ID); + + constructor() { super(); } + parse(value: string): NgbDateStruct | null { if (value) { const dateParts = value.trim().split('-'); diff --git a/npm/packs/jquery-validation/abp.resourcemapping.js b/npm/packs/jquery-validation/abp.resourcemapping.js index 91575842d3..a0ce7addcc 100644 --- a/npm/packs/jquery-validation/abp.resourcemapping.js +++ b/npm/packs/jquery-validation/abp.resourcemapping.js @@ -1,6 +1,7 @@ module.exports = { mappings: { "@node_modules/jquery-validation/dist/jquery.validate.js": "@libs/jquery-validation/", - "@node_modules/jquery-validation/dist/localization/*.*": "@libs/jquery-validation/localization/" + "@node_modules/jquery-validation/dist/localization/*.*": "@libs/jquery-validation/localization/", + "@node_modules/@abp/jquery-validation/src/*.*": "@libs/jquery-validation/" } } \ No newline at end of file diff --git a/npm/packs/jquery-validation/src/abp.jquery.validate.js b/npm/packs/jquery-validation/src/abp.jquery.validate.js new file mode 100644 index 0000000000..62b32d1d22 --- /dev/null +++ b/npm/packs/jquery-validation/src/abp.jquery.validate.js @@ -0,0 +1,27 @@ +$.validator.methods.range = function (value, element, param) { + if (this.optional(element)) { + return true; + } + + // Remove thousands separators and convert decimal separators + var cleanValue = value.replace(/[.,](?=.*[.,])/g, '').replace(/[.,](?!.*[.,])/g, '.'); + var numericValue = parseFloat(cleanValue); + + // Check if conversion was successful and value is within range + return !isNaN(numericValue) && (numericValue >= param[0] && numericValue <= param[1]); +}; + +$.validator.methods.number = function (value, element) { + if (this.optional(element)) { + return true; + } + + // 1. Period as decimal separator, comma as thousands separator (e.g., 1,234.56 or -1,234.56) + // 2. Comma as decimal separator, period as thousands separator (e.g., 1.234,56 or -1.234,56) + // 3. Pure numbers (integers) + var pattern1 = /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:-?\.\d+)?$/; + var pattern2 = /^(?:-?\d+|-?\d{1,3}(?:\.\d{3})+)?(?:-?,\d+)?$/; + var pattern3 = /^-?\d+$/; + + return pattern1.test(value) || pattern2.test(value) || pattern3.test(value); +} diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250717081711_Initial.Designer.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250717081711_Initial.Designer.cs index baa25c45a2..db85e2f5d5 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250717081711_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20250611122251_Initial")] + [Migration("20250717081711_Initial")] partial class Initial { /// @@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1452,8 +1455,8 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250717081711_Initial.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250717081711_Initial.cs index dd86268274..ca5e31e7c4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250611122251_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20250717081711_Initial.cs @@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -766,7 +767,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs index 2aa577257f..aa06ad1bbe 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1449,8 +1452,8 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250717081855_Initial.Designer.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250717081855_Initial.Designer.cs index daa422bbcc..961580c3c6 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250717081855_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20250611122409_Initial")] + [Migration("20250717081855_Initial")] partial class Initial { /// @@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1452,8 +1455,8 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250717081855_Initial.cs similarity index 99% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250717081855_Initial.cs index 12a56494ee..d4990d59a7 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250717081855_Initial.cs @@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -766,7 +767,7 @@ namespace MyCompanyName.MyProjectName.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs index 617cde4a48..d216c5d155 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1449,8 +1452,8 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250717081619_Initial.Designer.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250717081619_Initial.Designer.cs index 88488681cd..c90ff0bb2e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250717081619_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Host.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20250611122225_Initial")] + [Migration("20250717081619_Initial")] partial class Initial { /// @@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1452,8 +1455,8 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250717081619_Initial.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250717081619_Initial.cs index 293ec738c4..b08f2c0b80 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250611122225_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/20250717081619_Initial.cs @@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Host.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -766,7 +767,7 @@ namespace MyCompanyName.MyProjectName.Host.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs index a403280dbc..fffa45164c 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1449,8 +1452,8 @@ namespace MyCompanyName.MyProjectName.Host.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250717081642_Initial.Designer.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.Designer.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250717081642_Initial.Designer.cs index c55ee59fc5..5e5d98d72e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.Designer.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250717081642_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Mvc.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20250611122237_Initial")] + [Migration("20250717081642_Initial")] partial class Initial { /// @@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1452,8 +1455,8 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250717081642_Initial.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.cs rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250717081642_Initial.cs index 32de876108..fa3bbe7aed 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250611122237_Initial.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/20250717081642_Initial.cs @@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -766,7 +767,7 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs index 77eaf6b52c..5df74a87ff 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1449,8 +1452,8 @@ namespace MyCompanyName.MyProjectName.Mvc.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.Designer.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250717081721_Initial.Designer.cs similarity index 99% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.Designer.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250717081721_Initial.Designer.cs index 5a7190317f..4ee8dc1ff3 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.Designer.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250717081721_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(MyProjectNameDbContext))] - [Migration("20250611122258_Initial")] + [Migration("20250717081721_Initial")] partial class Initial { /// @@ -1285,6 +1285,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1508,8 +1511,8 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250717081721_Initial.cs similarity index 99% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.cs rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250717081721_Initial.cs index ec2bf59db0..579fe19395 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250611122258_Initial.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/20250717081721_Initial.cs @@ -449,6 +449,7 @@ namespace MyCompanyName.MyProjectName.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -788,7 +789,7 @@ namespace MyCompanyName.MyProjectName.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs index 8fb9ea0266..51c2b336bc 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations/MyProjectNameDbContextModelSnapshot.cs @@ -1282,6 +1282,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1505,8 +1508,8 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250717081803_Initial.Designer.cs similarity index 99% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250717081803_Initial.Designer.cs index 2566ba0bb8..0d0867486b 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250611122321_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250717081803_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(AuthServerDbContext))] - [Migration("20250611122321_Initial")] + [Migration("20250717081803_Initial")] partial class Initial { /// @@ -1229,6 +1229,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1452,8 +1455,8 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250717081803_Initial.cs similarity index 99% rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250717081803_Initial.cs index 12a56494ee..d4990d59a7 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Migrations/20250611122409_Initial.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/20250717081803_Initial.cs @@ -427,6 +427,7 @@ namespace MyCompanyName.MyProjectName.Migrations RedirectUris = table.Column(type: "nvarchar(max)", nullable: true), Requirements = table.Column(type: "nvarchar(max)", nullable: true), Settings = table.Column(type: "nvarchar(max)", nullable: true), + FrontChannelLogoutUri = table.Column(type: "nvarchar(max)", nullable: true), ClientUri = table.Column(type: "nvarchar(max)", nullable: true), LogoUri = table.Column(type: "nvarchar(max)", nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), @@ -766,7 +767,7 @@ namespace MyCompanyName.MyProjectName.Migrations ReferenceId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Status = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true), - Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + Type = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false), ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) }, diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs index 8c0f1e1e4c..a9dc5b7efa 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/Migrations/AuthServerDbContextModelSnapshot.cs @@ -1226,6 +1226,9 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(max)") .HasColumnName("ExtraProperties"); + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(max)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -1449,8 +1452,8 @@ namespace MyCompanyName.MyProjectName.Migrations .HasColumnType("nvarchar(400)"); b.Property("Type") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); b.HasKey("Id"); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250611122311_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250717081744_Initial.Designer.cs similarity index 99% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250611122311_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250717081744_Initial.Designer.cs index 9b8b4d619a..956e6e89d1 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250611122311_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250717081744_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20250611122311_Initial")] + [Migration("20250717081744_Initial")] partial class Initial { /// diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250611122311_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250717081744_Initial.cs similarity index 100% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250611122311_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Migrations/20250717081744_Initial.cs diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250611122327_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250717081812_Initial.Designer.cs similarity index 96% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250611122327_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250717081812_Initial.Designer.cs index 754d85b5a2..3cd1db524a 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250611122327_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250717081812_Initial.Designer.cs @@ -12,7 +12,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(MyProjectNameHttpApiHostMigrationsDbContext))] - [Migration("20250611122327_Initial")] + [Migration("20250717081812_Initial")] partial class Initial { /// diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250611122327_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250717081812_Initial.cs similarity index 100% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250611122327_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/Migrations/20250717081812_Initial.cs diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250611122337_Initial.Designer.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250717081828_Initial.Designer.cs similarity index 99% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250611122337_Initial.Designer.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250717081828_Initial.Designer.cs index 2756075b5f..0746c415d6 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250611122337_Initial.Designer.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250717081828_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace MyCompanyName.MyProjectName.Migrations { [DbContext(typeof(UnifiedDbContext))] - [Migration("20250611122337_Initial")] + [Migration("20250717081828_Initial")] partial class Initial { /// diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250611122337_Initial.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250717081828_Initial.cs similarity index 100% rename from templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250611122337_Initial.cs rename to templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/Migrations/20250717081828_Initial.cs