diff --git a/Directory.Packages.props b/Directory.Packages.props index 5a3c4bdd7e..74579541f2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,7 @@ - + @@ -128,11 +128,11 @@ - - - - - + + + + + @@ -145,6 +145,7 @@ + @@ -155,8 +156,8 @@ - - + + diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index eebf13731a..62f8304467 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -721,6 +721,60 @@ "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", + "Menu:Solution": "Solution", + "Enum:LicenseType:1": "Personal", + "Enum:LicenseType:2": "Team", + "Enum:LicenseType:3": "Business", + "Enum:LicenseType:4": "Enterprise", + "Enum:Template:0": "Unknown", + "Enum:Template:1": "App No Layers", + "Enum:Template:2": "App Layered", + "Enum:Template:3": "Microservice", + "Enum:UiFramework:0": "Unknown", + "Enum:UiFramework:1": "None", + "Enum:UiFramework:2": "Mvc Razor Pages", + "Enum:UiFramework:3": "Angular", + "Enum:UiFramework:4": "Blazor Wasm", + "Enum:UiFramework:5": "Blazor Server", + "Enum:UiFramework:6": "Blazor Web App", + "Enum:UiFramework:7": "Blazor MaUI", + "Enum:DatabaseProvider:0": "Unknown", + "Enum:DatabaseProvider:1": "None", + "Enum:DatabaseProvider:2": "EfCore", + "Enum:DatabaseProvider:3": "MongoDb", + "Enum:Dbms:0": "Unknown", + "Enum:Dbms:1": "None", + "Enum:Dbms:2": "SqlServer", + "Enum:Dbms:3": "PostgreSql", + "Enum:Dbms:4": "Oracle", + "Enum:Dbms:5": "OracleDevart", + "Enum:Dbms:6": "MySql", + "Enum:Dbms:7": "Sqlite", + "Enum:UiTheme:0": "Unknown", + "Enum:UiTheme:1": "None", + "Enum:UiTheme:2": "Basic", + "Enum:UiTheme:3": "LeptonX", + "Enum:UiTheme:4": "LeptonX Lite", + "Enum:UiThemeStyle:0": "Unknown", + "Enum:UiThemeStyle:1": "System", + "Enum:UiThemeStyle:2": "Dim", + "Enum:UiThemeStyle:3": "Dark", + "Enum:UiThemeStyle:4": "Light", + "Enum:MobileApp:0": "Unknown", + "Enum:MobileApp:1": "None", + "Enum:MobileApp:2": "Maui", + "Enum:MobileApp:3": "ReactNative", + "Enum:CreationTool:0": "Unknown", + "Enum:CreationTool:1": "StudioUI", + "Enum:CreationTool:2": "StudioCli", + "Enum:CreationTool:3": "OldCli", + "Menu:TelemetryMenu": "Telemetry Reports", + "Menu:Studio": "Studio", + "Menu:Solutions": "Solutions", + "Menu:Users": "Users", + "Menu:UserReports": "Users" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index f5fea908de..abd7184278 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -431,6 +431,9 @@ "WhoWeAre_Expert": "About Me", "CreateSolutionFolder": "Create Solution Folder", "CreateSolutionFolderOption": "Specifies if the project will be in a new folder in the output folder or directly the output folder.", + "CreateCrudPage": "Create CRUD Page", + "CreateCrudPageOption": "Generates a sample CRUD page with a Book entity to demonstrate basic operations (Create, Read, Update, Delete).", + "ConnectionString": "Connection string", "BooksPageTitle": "ABP Books", "BooksPageDescription": "Explore ABP books to deepen your understanding and mastery of ABP.", "PackageDetailPage_NuGetPackageInstallationOptions": "There are three ways to install {0} NuGet package to your project", @@ -534,7 +537,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.", @@ -907,6 +910,7 @@ "ProudToWorkWith": "Proud to Work With", "JoinOurConsumers": "Join them and build amazing products fast.", "AdditionalServicesExplanation": "Do you need additional or custom services? We and our partners can provide;", + "CustomLicense": "Custom License", "CustomProjectDevelopment": "Custom Project Development", "CustomProjectDevelopmentExplanation": "Dedicated developers for your custom projects.", "PortingExistingProjects": "Porting Existing Projects", @@ -1059,7 +1063,7 @@ "BuyNow": "Buy Now", "PayViaAmexCard": "How can I pay via my AMEX card?", "PayViaAmexCardDescription": "The default payment gateway 'Iyzico' may decline some AMEX credit cards due to security measures. In this case, you can pay through the alternative payment gateway '2Checkout'.", - "InvalidReCaptchaErrorMessage": "There was an error verifying reCAPTCHA. Please try again.", + "InvalidReCaptchaErrorMessage": "There was an error verifying reCAPTCHA.", "YourCompanyName": "Your company name", "FirstName": "First name", "LastName": "Last name", @@ -1425,7 +1429,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", @@ -1795,7 +1799,7 @@ "SpecialDiscount": "Special Discount", "YourOrganizationOverview": "Your Organization Overview", "TrainingDetailsHeaderInfo_TrainingHourSingular": "{0} hour", - "ContactPageError": "Please send your message via email to info@abp.io
Here's what you wrote :", + "ContactPageError": "You can also send your message via email. Copy your message below and send to info@abp.io ", "GoBack": "Go back", "HereWhatYouWrote": "Here's what you wrote :", "Sales": "Sales", @@ -1891,6 +1895,21 @@ "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.", + "CanIUseABPProductsOnMoreThanOneComputer": "Can I use ABP products on more than one computer?", + "ABPProductsOnMoreThanOneComputerExplanation": "Yes. Each developer can install the software on up to two machines. A third machine requires approval via email. When you stop using one of your computers, the system understands and automatically invalidates that computer from your paired computer list.", + "CanIShareTheLicensedABPCommercialProducts": "Can I share the licensed ABP commercial products publicly or make them open source?", + "ShareTheLicensedABPCommercialProductsExplanation": "No! Sharing or sublicensing a Commercial (PRO) ABP package is strictly prohibited.", + "AreSubscriptionRenewalsAutomatic": "Are subscription renewals automatic?", + "SubscriptionRenewalsAutomaticExplanation": "By default, no, the renewals are manual. On the other hand, when you purchase a new license and use the payment gateway 'Iyzico' with your credit card, you will see a checkbox called 'Automatic Renewal' in the purchase steps which allows ABP system automatically renew your license. When you check that checkbox, the auto-renewal process lets you renew your license without losing this discount, and your development will never be interrupted. ABP does not save your credit card information, but our payment gateway does secure savings. You can disable auto-renewal at any time by accessing your Organization Management page.", + "DoYouProvideSupportForThird-partyLibraries": "Do you provide support for third-party libraries?", + "ProvideSupportForThird-partyExplanation": "No. Support only covers ABP Framework, ABP commercial packages and products which have been created by Volosoft.", + "DoYouSupportCustomABPArchitectures": "Do you support custom ABP architectures?", + "SupportCustomABPArchitecturesExplanation": "No. Support is only provided for standard ABP solution structures. On the other hand, you can always get support for your custom needs with a paid consultancy from the ABP Team.", + "DoesABPCollectAnyPersonalOrTechnicalData": "Does ABP collect any personal or technical data?", + "ABPCollectAnyDataExplanation": "The software may collect information about you and your use of the software, and send that to Volosoft. Volosoft as the software and service provider may use this information to provide services and improve its products & services. You may opt-out of these scenarios, as described in the EULA under PRIVACY AND COLLECTION OF PERSONAL DATA topic ." } } diff --git a/common.props b/common.props index b406cf6efc..bf99a29ca2 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 9.3.1 - 4.3.1 + 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..7cd0f2aa47 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..8539d5be5c 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..2502d8904c 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..372f41e3a5 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..9d98fe4585 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..dac9184cf3 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..af4c2b300a 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..e2c3a297f7 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..bc4220f83a 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..e5dd4c0c9d 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..d2ae92ff0e 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..22c32abd22 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..392a680bdf 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..15a3fbf143 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..595446cefa 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..8b8f3a978b 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/Blog-Posts/2025-08-08 v9_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/POST.md new file mode 100644 index 0000000000..3acf9a7dd0 --- /dev/null +++ b/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/POST.md @@ -0,0 +1,79 @@ +# ABP.IO Platform 9.3 Final Has Been Released! + +We are glad to announce that [ABP](https://abp.io/) 9.3 stable version has been released today. + +## What's New With Version 9.3? + +All the new features were explained in detail in the [9.3 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-9-3-release-candidate-4dqgiryf), so there is no need to review them again. You can check it out for more details. + +## Getting Started with 9.3 + +### Creating New Solutions + +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) to create new solutions. + +> **Note**: ABP Studio **v1.2.1** has been released with support for **ABP 9.3**. If you already have ABP Studio installed, update it to v1.2.1 (or later, if available) to create new applications targeting 9.3. ABP Studio checks for updates automatically and will prompt you in-app modal to update to the latest version, or you can download the latest installer from the [Studio](https://abp.io/studio) page. See the [upgrading guide](https://abp.io/docs/latest/studio/installation#upgrading) for details. After updating, the New Solution wizard will create applications with ABP 9.3 by default. You can check the [ABP Studio and ABP Startup Template Version Mappings](https://abp.io/docs/latest/studio/version-mapping) documentation to see the corresponding ABP versions for other versions of Studio. + +### How to Upgrade an Existing Solution + +You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: + +### Upgrading via ABP Studio + +If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. + +After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution: + +![](upgrade-abp-packages.png) + +### Upgrading via ABP CLI + +Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. + +If you haven't installed it yet, you can run the following command: + +```bash +dotnet tool install -g Volo.Abp.Studio.Cli +``` + +Or to update the existing CLI, you can run the following command: + +```bash +dotnet tool update -g Volo.Abp.Studio.Cli +``` + +After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: + +```bash +abp update +``` + +You can run this command in the root folder of your solution to update all ABP related packages. + +## Migration Guides + +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: [ABP Version 9.3 Migration Guide](https://abp.io/docs/9.3/release-info/migration-guides/abp-9-3) + +## Community News + +### New ABP Community Articles + +As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: + +* [Fahri Gedik](https://abp.io/community/members/fahrigedik) has published 2 new articles: + * [A Modern Approach to Angular Dependency Injection using inject function](https://abp.io/community/articles/a-modern-approach-to-angular-dependency-injection-using-8np4o1ap) + * [Angular Application Builder: Transitioning from Webpack to Esbuild](https://abp.io/community/articles/angular-application-builder-transitioning-from-webpack-to-3yzhzfl0) +* [Benjamin Fadina](https://abp.io/community/members/benjaminsqlserver@gmail.com) has published several videos on various topics such as **Blazor Web Assembly Using ABP.IO**, **CQRS Implementation with MediatR in ABP** and more. You can see all his videos [here](https://abp.io/community/members/benjaminsqlserver@gmail.com). +* [Mansur Besleney](https://abp.io/community/members/mansur.besleney) has published [How to Build Persistent Background Jobs with ABP Framework and Quartz](https://abp.io/community/articles/how-to-build-persistent-background-jobs-with-abp-framework-n9aloh93) +* [Halil Ibrahim Kalkan](https://x.com/hibrahimkalkan) has published [Multitenancy with Separate Databases in .NET and ABP](https://abp.io/community/articles/multitenancy-with-separate-databases-in-dotnet-and-abp-51nvl4u9) +* [Alex Maiereanu](https://abp.io/community/members/alex.maiereanu@3sstudio.com) has published [ABP-Hangfire-AzurePostgreSQL](https://abp.io/community/articles/abphangfireazurepostgresql-s1jnf3yg) +* [Jack Fistelmann](https://abp.io/community/members/jfistelmann) has published [ABP and maildev](https://abp.io/community/articles/abp-and-maildev-gy13cr1p) +* [Harsh Gupta](https://abp.io/community/members/harshgupta) has published [How to Add a Module in the ABP.io Application?](https://abp.io/community/articles/how-to-add-a-module-in-the-abp.io-application-sdeajkn6) +* [Tarık Özdemir](https://abp.io/community/members/mtozdemir) has published [AI-First Architecture for .NET Projects: A Modern Blueprint Inspired by McKinsey](https://abp.io/community/articles/AI-First%20Architecture%20for%20.NET%20Projects%3A%20A%20Modern%20Blueprint-h2wgcoq3) +* [Liming Ma](https://github.com/maliming) has published [Using Hangfire Dashboard in ABP API Website](https://abp.io/community/articles/using-hangfire-dashboard-in-abp-api-website--r32ox497) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. + +## About the Next Version + +The next feature version will be 10.0. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/cover-image.png new file mode 100644 index 0000000000..291da0d04f Binary files /dev/null and b/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/cover-image.png differ diff --git a/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/upgrade-abp-packages.png b/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/upgrade-abp-packages.png new file mode 100644 index 0000000000..ad5e9bd462 Binary files /dev/null and b/docs/en/Blog-Posts/2025-08-08 v9_3_Release_Stable/upgrade-abp-packages.png differ diff --git a/docs/en/Community-Articles/2022-04-18-abp-community-talks-20223/post.md b/docs/en/Community-Articles/2022-04-18-abp-community-talks-20223/post.md index fcd7043594..5d9c9e0d3e 100644 --- a/docs/en/Community-Articles/2022-04-18-abp-community-talks-20223/post.md +++ b/docs/en/Community-Articles/2022-04-18-abp-community-talks-20223/post.md @@ -6,7 +6,7 @@ * ABP Community Talks are scheduled to be held on a monthly basis. * ABP Community Talks are and always will be completely free to attend. Everyone is welcome to join, ask questions and make suggestions before, during and after the event. * ABP Community Talks are created and announced on [Kommunity](https://kommunity.com/volosoft/events). - * ABP Community Talks are announced regularly on [ABP Framework Twitter Account](https://twitter.com/abpframework), [Volosoft LinkedIn account](https://www.linkedin.com/company/volosoft), [Volosoft Facebook Account](https://www.facebook.com/volosoftcompany), [ABP Community Discord Server](https://discord.gg/CrYrd5vcGh). We highly encourage everyone to follow us and make suggestions. + * ABP Community Talks are announced regularly on [ABP Framework Twitter Account](https://twitter.com/abpframework), [Volosoft LinkedIn account](https://www.linkedin.com/company/volosoft), [Volosoft Facebook Account](https://www.facebook.com/volosoftcompany), [ABP Community Discord Server](https://abp.io/join-discord). We highly encourage everyone to follow us and make suggestions. * ABP Community Talks are available to watch after the event on YouTube. See [ABP Community Talks YouTube Playlist](https://www.youtube.com/playlist?list=PLsNclT2aHJcOsPustEkzG6DywiO8eh0lB). # ABP Community Talks 2022.3 diff --git a/docs/en/Community-Articles/2022-04-19-official-abp-discord-server-is-here/post.md b/docs/en/Community-Articles/2022-04-19-official-abp-discord-server-is-here/post.md index 5d099cc1c3..ca765a5860 100644 --- a/docs/en/Community-Articles/2022-04-19-official-abp-discord-server-is-here/post.md +++ b/docs/en/Community-Articles/2022-04-19-official-abp-discord-server-is-here/post.md @@ -1,9 +1,9 @@ - We are excited to announce Official ABP Discord Server is created! You can join the ABP Discord Community by clicking [here](https://discord.gg/wbcQAsUrs9). + We are excited to announce Official ABP Discord Server is created! You can join the ABP Discord Community by clicking [here](https://abp.io/join-discord). In the first week of opening ABP Discord Server, member amount reached more than 500. We are grateful to and blessed by your interest. Thanks to all of you! This also made us sure that an ABP Discord Server was actually a need for the community members to interact with each other. ABP Community is growing by the second, and we are grateful for all your contributions towards ABP Framework. We noticed that ABP Community’s communication were significant on ABP Framework’s GitHub, we wanted to take it to the next level and have an area where all of us can easily chat with each other. -> [Join ABP Discord Server Now](https://discord.gg/wbcQAsUrs9) +> [Join ABP Discord Server Now](https://abp.io/join-discord) # What Can You Do on ABP Community Discord Server? @@ -42,11 +42,11 @@ # How Can You Join To ABP Discord Server? - You can join ABP Discord Server by simply clicking to [https://discord.gg/abp](https://discord.gg/wbcQAsUrs9). + You can join ABP Discord Server by simply clicking to [https://abp.io/join-discord](https://abp.io/join-discord). We are excited to welcome you in ABP Discord Server! -> [Click Here to Join ABP Discord Server Now](https://discord.gg/wbcQAsUrs9) +> [Click Here to Join ABP Discord Server Now](https://abp.io/join-discord) ### What is Discord? @@ -62,4 +62,4 @@ In Discord Servers, users communicate with each other in a way that is convenient for them. Discord allows people to make voice calls, video chats, or simply text messages. Communities are created by wether fans of a specific topic(games, open-source frameworks, NFT, etc.) or by the official authorities of that specific topic(game creator, framework core team, creator of a token, etc). - In ABP Community Discord Server’s case, it is a server created by official authorities with core team being present in the server along with the community members. Even though it is created for the framework community members to communicate with each other easily, everyone who is interested in following the latest news about ABP Platform are welcome to [join ABP Discord Server](https://discord.gg/wbcQAsUrs9)! \ No newline at end of file + In ABP Community Discord Server’s case, it is a server created by official authorities with core team being present in the server along with the community members. Even though it is created for the framework community members to communicate with each other easily, everyone who is interested in following the latest news about ABP Platform are welcome to [join ABP Discord Server](https://abp.io/join-discord)! \ No newline at end of file diff --git a/docs/en/Community-Articles/2022-05-10-abpio-platform-53-rc-has-been-published/post.md b/docs/en/Community-Articles/2022-05-10-abpio-platform-53-rc-has-been-published/post.md index eb157010b8..cc0e57db93 100644 --- a/docs/en/Community-Articles/2022-05-10-abpio-platform-53-rc-has-been-published/post.md +++ b/docs/en/Community-Articles/2022-05-10-abpio-platform-53-rc-has-been-published/post.md @@ -254,4 +254,4 @@ We've created an official ABP Discord server so the ABP Community can interact w Thanks to the ABP Community, **700+** people joined our Discord Server so far and it grows every day. -You can join our Discord Server from [here](https://discord.gg/abp), if you haven't yet. \ No newline at end of file +You can join our Discord Server from [here](https://abp.io/join-discord), if you haven't yet. \ No newline at end of file diff --git a/docs/en/Community-Articles/2023-02-21-abp-year-review-2022-wrap-up/post.md b/docs/en/Community-Articles/2023-02-21-abp-year-review-2022-wrap-up/post.md index 6abf2f3d12..a27aeb9f2c 100644 --- a/docs/en/Community-Articles/2023-02-21-abp-year-review-2022-wrap-up/post.md +++ b/docs/en/Community-Articles/2023-02-21-abp-year-review-2022-wrap-up/post.md @@ -1,100 +1,200 @@ -

ABP Framework is an open source infrastructure that enables developers to create modern web applications by following the best practices and conventions of software development. In 2022, ABP Framework continued to thrive, achieving significant milestones and making waves in the software development community. With more than 9K GitHub stars and over 10 millions of downloads on NuGet, ABP Framework has become a go-to framework for developers seeking a reliable and efficient way to build web applications.

- -

As ABP Team, we owe our success to our vibrant community, and we are immensely grateful for the support and contributions of each and every member. With your help, we achieved a lot in 2022. We remained committed to our values of transparency, openness, and collaboration, engaging with our community members as much as possible to ensure that we are creating a framework that meets their needs.

- -

One of the major highlights of 2022 was the release of .NET Core 7, which provided a powerful platform for ABP Framework to build upon. Additionally, ABP Commercial and our training programs continued to help developers and businesses to leverage the power of the ABP Framework, enabling them to build modern web applications more efficiently and effectively than ever before.

- -

In this article, we'll take a closer look at the key highlights of 2022 for ABP Framework, from major updates to achivements and the community insights. We are excited to share our progress with you and provide insights into how ABP Framework is continuing to shape the future of software development. So, let's dive in!

- - - - -

NuGet Downloads

-

NuGet is a package manager designed specifically for the .NET ecosystem. It simplifies the process of creating and consuming packages, thanks to the NuGet client tools. By using these tools, developers can easily manage their project dependencies and improve their workflow.

-

In 2022, ABP Core NuGet package reached more than 10 million of downloads!

-

On the other hand, overall Volosoft NuGet Packages reached more than half a billion downloads!

-

Thank you all for your interest and support towards Volosoft and ABP packages.

- - -

E-Books

-

Our published e-book amount is reached 3! This year, with our founder Halil İbrahim Kalkan's contributions we now have 3 published e-books.

- - - -

Tutorial Videos

-

In 2022, we tried to be as much active as we could. To give you more insight and let you understand ABP Framework with short videos according to your interests, we published 48 tutorial videos. Though the videos were created by overall team members of ABP Framework, someone deserves a special mention here. Shout out to our ABP Core Team member Hamza Albreem for his hard work.

- - -

GitHub Stars

-

ABP Framework GitHub repository reached more than 9K stars. We appreciate your interest and support for ABP Framework GitHub repository. We are working hard to be worthy of your interest and reach out to more people to simplify and streamline their development processes.

- -

Community Talks

-

ABP Community Talks is our monthly event that brings together members of the ABP Framework community to discuss and exchange ideas. Prior to each event, we collect suggestions from our contributors, monitor trending topics in the industry, and review updates and news related to the ABP Platform to curate the topics for discussion. Once the topics are finalized, we announce them through our social media and community channels to ensure everyone is aware and can join in on the conversation.

-

We did 10 ABP Community Talks Episodes of and 1 ABP Suite webinar. You can take a look at them and check out our videos we have on Volosoft YouTube Channel.

- -

ABP Community Contributions

-

The ABP Community is a hub that offers resources such as articles, video tutorials, and updates on ABP's development progress and events for ABP Framework, .NET, and software development. Developers can also connect with others, help each other, and share their expertise in ABP Community.

-
    You can check out each source from the list below. -
  • ABP Community Events: You can reach them from here.
  • -
  • ABP Community Posts: You can reach them from here
  • -
  • ABP Community Videos: You can reach them from here.
  • -
  • ABP Community Stackoverflow: You can reach them from here.
  • -
-

In 2022, the community's contribution reached a point where more than 100 resources. Thank you for all your effort! Please keep it going! It is becoming a more and more rich resource thanks to your variety of contributions and help.

- - -

ABP Community Discord Server

-

To take community interaction to the next level, we created the official ABP Discord server, providing a platform for the ABP Community to connect and communicate instantly through chatting.

-

We were so excited announcing the official ABP Discord Server. In the first week of announcing it, the server quickly attracted over 500 members. We're grateful for your interest and support, which confirms the need for a dedicated platform for community interaction.

-> Join ABP Discord Server Now - - -

ABP Framework GitHub Contributions

-

In 2022, ABP Core Team worked hard to achieve milestones and give the best value with ABP Framework so users can benefit from its features. Additional to our team's work, ABP Framework is perfected in 2022 with ABP Community members' contributions, 3157 commits pushed from 48 different contributors.

-

We appreciate your hard work and effort you put into making ABP Framework better and improved.

- - -

Events/Summits

-

We try to contribute to the developers community as much as we can since day 1. This year was no different. We tried to give value through sponsorships for developer communities. Especially with us leaving the pandemic behind every day, we try to keep up with the in-person events as well as online events. We plan to do more in next year. So, stay tuned!

-

This year, we sponsored to 4 events. They were, DevNot -Designing Monolith First for Microservice Architecture event, DNF Summit 2022, Developer Summit 2022, and .NET Conference 2022. - - -

ABP Releases

-

ABP Framework released 4 versions from 5.1 to 7.1 in 2022. You can check the release logs from ABP Framework Release Logs.

-

The most important milestone in these releases is that we upgraded ABP Framework to .NET 7.0 in ABP v7.0.

-

Additionally, we switched to OpenIddict for the startup templates in ABP v6.0.

- - -

ABP Commercial

-

It has been a successful year for ABP Commercial as well as ABP Framework. We have already reached to more than 100 countries over the years of ABP Commercial's release. This year, we continued to be streamline businesses' development processes with ABP Commercial.

-
    -
  • We have served to different sizes of businesses from more than 50 countries and more than 40 industries .
  • -
  • We performed 286 hours of training to simplify users' learning curve of ABP Framework.
  • -
  • 1771 support tickets resolved in the premium support forum in which ABP Commercial users can ask their questions directly to ABP Core Team members via ABP Commercial Support Center in addition to community support we provide for ABP Framework users/developers.
  • -
  • We received 39 new testimonials, all from satisfied customers which led us to the other headline, Gartner Badges.
  • -
- - -

LeptonX Theme

-

The Lepton Theme is a module that offers a theme for abp.io-based applications, featuring an Admin Dashboard designed by the ABP Platform. We released a version we called LeptonX Theme which is an upgraded version of Lepton Theme. You can view a live preview of the LeptonX Theme. While the LeptonX theme is currently exclusive to ABP Commercial users, ABP Framework users can still access the Lite version. You can see the documentation for ABP LeptonX Theme light from here.

- - -

Gartner Badges

-

Gartner badges are given as an award to the listed softwares within their software review/suggestion platforms. To be able to get these awards, certain criterias have to be met such as ease of use, likelihood of recommend, functionality, etc. and they are calculated completely according to the users' real reviews.

-

In 2022, ABP Commercial reached to such success thanks to its users' support on Gartner, it has been recognized with 2 badges in Application Development category.

- -

Thank you all for all these recognition you deemed us worthy of.

+

ABP Framework is an open source infrastructure that enables developers to create modern web applications by following the best practices and conventions of software development. In 2022, ABP Framework continued to thrive, achieving significant milestones and making waves in the software development community. With more than 9K GitHub stars and over 10 millions of downloads on NuGet, ABP Framework has become a go-to framework for developers seeking a reliable and efficient way to build web applications.

+ + + +

As ABP Team, we owe our success to our vibrant community, and we are immensely grateful for the support and contributions of each and every member. With your help, we achieved a lot in 2022. We remained committed to our values of transparency, openness, and collaboration, engaging with our community members as much as possible to ensure that we are creating a framework that meets their needs.

+ + + +

One of the major highlights of 2022 was the release of .NET Core 7, which provided a powerful platform for ABP Framework to build upon. Additionally, ABP Commercial and our training programs continued to help developers and businesses to leverage the power of the ABP Framework, enabling them to build modern web applications more efficiently and effectively than ever before.

+ + + +

In this article, we'll take a closer look at the key highlights of 2022 for ABP Framework, from major updates to achivements and the community insights. We are excited to share our progress with you and provide insights into how ABP Framework is continuing to shape the future of software development. So, let's dive in!

+ + + + + + + + + +

NuGet Downloads

+ +

NuGet is a package manager designed specifically for the .NET ecosystem. It simplifies the process of creating and consuming packages, thanks to the NuGet client tools. By using these tools, developers can easily manage their project dependencies and improve their workflow.

+ +

In 2022, ABP Core NuGet package reached more than 10 million of downloads!

+ +

On the other hand, overall Volosoft NuGet Packages reached more than half a billion downloads!

+ +

Thank you all for your interest and support towards Volosoft and ABP packages.

+ + + + + +

E-Books

+ +

Our published e-book amount is reached 3! This year, with our founder Halil İbrahim Kalkan's contributions we now have 3 published e-books.

+ + + + + + + +

Tutorial Videos

+ +

In 2022, we tried to be as much active as we could. To give you more insight and let you understand ABP Framework with short videos according to your interests, we published 48 tutorial videos. Though the videos were created by overall team members of ABP Framework, someone deserves a special mention here. Shout out to our ABP Core Team member Hamza Albreem for his hard work.

+ + + + + +

GitHub Stars

+ +

ABP Framework GitHub repository reached more than 9K stars. We appreciate your interest and support for ABP Framework GitHub repository. We are working hard to be worthy of your interest and reach out to more people to simplify and streamline their development processes.

+ + + +

Community Talks

+ +

ABP Community Talks is our monthly event that brings together members of the ABP Framework community to discuss and exchange ideas. Prior to each event, we collect suggestions from our contributors, monitor trending topics in the industry, and review updates and news related to the ABP Platform to curate the topics for discussion. Once the topics are finalized, we announce them through our social media and community channels to ensure everyone is aware and can join in on the conversation.

+ +

We did 10 ABP Community Talks Episodes of and 1 ABP Suite webinar. You can take a look at them and check out our videos we have on Volosoft YouTube Channel.

+ + + +

ABP Community Contributions

+ +

The ABP Community is a hub that offers resources such as articles, video tutorials, and updates on ABP's development progress and events for ABP Framework, .NET, and software development. Developers can also connect with others, help each other, and share their expertise in ABP Community.

+ +
    You can check out each source from the list below. + +
  • ABP Community Events: You can reach them from here.
  • + +
  • ABP Community Posts: You can reach them from here
  • + +
  • ABP Community Videos: You can reach them from here.
  • + +
  • ABP Community Stackoverflow: You can reach them from here.
  • + +
+ +

In 2022, the community's contribution reached a point where more than 100 resources. Thank you for all your effort! Please keep it going! It is becoming a more and more rich resource thanks to your variety of contributions and help.

+ + + + + +

ABP Community Discord Server

+ +

To take community interaction to the next level, we created the official ABP Discord server, providing a platform for the ABP Community to connect and communicate instantly through chatting.

+ +

We were so excited announcing the official ABP Discord Server. In the first week of announcing it, the server quickly attracted over 500 members. We're grateful for your interest and support, which confirms the need for a dedicated platform for community interaction.

+ +> Join ABP Discord Server Now + + + + + +

ABP Framework GitHub Contributions

+ +

In 2022, ABP Core Team worked hard to achieve milestones and give the best value with ABP Framework so users can benefit from its features. Additional to our team's work, ABP Framework is perfected in 2022 with ABP Community members' contributions, 3157 commits pushed from 48 different contributors.

+ +

We appreciate your hard work and effort you put into making ABP Framework better and improved.

+ + + + + +

Events/Summits

+ +

We try to contribute to the developers community as much as we can since day 1. This year was no different. We tried to give value through sponsorships for developer communities. Especially with us leaving the pandemic behind every day, we try to keep up with the in-person events as well as online events. We plan to do more in next year. So, stay tuned!

+ +

This year, we sponsored to 4 events. They were, DevNot + +Designing Monolith First for Microservice Architecture event, DNF Summit 2022, Developer Summit 2022, and .NET Conference 2022. + + + + + +

ABP Releases

+ +

ABP Framework released 4 versions from 5.1 to 7.1 in 2022. You can check the release logs from ABP Framework Release Logs.

+ +

The most important milestone in these releases is that we upgraded ABP Framework to .NET 7.0 in ABP v7.0.

+ +

Additionally, we switched to OpenIddict for the startup templates in ABP v6.0.

+ + + + + +

ABP Commercial

+ +

It has been a successful year for ABP Commercial as well as ABP Framework. We have already reached to more than 100 countries over the years of ABP Commercial's release. This year, we continued to be streamline businesses' development processes with ABP Commercial.

+ +
    + +
  • We have served to different sizes of businesses from more than 50 countries and more than 40 industries .
  • + +
  • We performed 286 hours of training to simplify users' learning curve of ABP Framework.
  • + +
  • 1771 support tickets resolved in the premium support forum in which ABP Commercial users can ask their questions directly to ABP Core Team members via ABP Commercial Support Center in addition to community support we provide for ABP Framework users/developers.
  • + +
  • We received 39 new testimonials, all from satisfied customers which led us to the other headline, Gartner Badges.
  • + +
+ + + + + +

LeptonX Theme

+ +

The Lepton Theme is a module that offers a theme for abp.io-based applications, featuring an Admin Dashboard designed by the ABP Platform. We released a version we called LeptonX Theme which is an upgraded version of Lepton Theme. You can view a live preview of the LeptonX Theme. While the LeptonX theme is currently exclusive to ABP Commercial users, ABP Framework users can still access the Lite version. You can see the documentation for ABP LeptonX Theme light from here.

+ + + + + +

Gartner Badges

+ +

Gartner badges are given as an award to the listed softwares within their software review/suggestion platforms. To be able to get these awards, certain criterias have to be met such as ease of use, likelihood of recommend, functionality, etc. and they are calculated completely according to the users' real reviews.

+ +

In 2022, ABP Commercial reached to such success thanks to its users' support on Gartner, it has been recognized with 2 badges in Application Development category.

+ + + +

Thank you all for all these recognition you deemed us worthy of.

+ diff --git a/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md b/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md index 33dd9eb292..e5196dde34 100644 --- a/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md +++ b/docs/en/Community-Articles/2024-11-25-Global-Assets/POST.md @@ -51,7 +51,7 @@ public class MyBlazorWebAssemblyBundlingModule : AbpModule options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global).AddContributors(typeof(MyModuleBundleScriptContributor)); // Style Bundles - options.ScriptBundles.Get(BlazorWebAssemblyStandardBundles.Scripts.Global).AddContributors(typeof(MyModuleBundleStyleBundleContributor)); + options.StyleBundles.Get(BlazorWebAssemblyStandardBundles.Styles.Global).AddContributors(typeof(MyModuleBundleStyleBundleContributor)); }); } } 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/Community-Articles/2025-07-26-Separate-Tenant-Schema/Drawings.pptx b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/Drawings.pptx new file mode 100644 index 0000000000..7a59c08c25 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/Drawings.pptx differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md new file mode 100644 index 0000000000..e63db935a5 --- /dev/null +++ b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/POST.md @@ -0,0 +1,480 @@ +# Multi-Tenancy with Separate Databases in .NET and ABP Framework + +[Multi-tenancy](https://abp.io/architecture/multi-tenancy) is a common architectural concept for modern SaaS applications, enabling a single application to serve multiple customers (each known as a tenant) while maintaining data isolation, scalability, and operational efficiency. The "Separate database per tenant" approach offers the highest level of data isolation, making it ideal for scenarios with strict data privacy, security, and performance requirements. + +In this article, we’ll explore how to use this advanced multi-tenancy model using the powerful capabilities of the ABP Framework and the .NET platform. + +> In this article, I will use [ABP Studio](https://abp.io/studio) for creating the application. ABP Studio allows to select "separate database per tenant" option only for [commercial licenses](https://abp.io/pricing). + +## Understanding Database Models for a Multi-Tenant Application + +In the next sections, I will explain various models for database models of a multi-tenant solution: + +* Single (shared) Database Model +* Separate Tenant Databases Model +* Hybrid Multi-Tenant Database Model + +Let's start with the first one... + +### Single (shared) Database Model + +In the shared database model, all the application data stored in a single physical database. In the following diagram, you see different kind of users use the application, and the application stored their data in a main database: + +![single-shared-database](single-shared-database.png) + +This is the default behavior when you [create a new ABP application](https://abp.io/docs/latest/get-started), because it is simple to begin with and proper for most applications. + +In this model, a single database table may contain data of multiple tenants. Each row in these tables have a `TenantId` field which is used to distinguish the tenant data and isolate a tenant's data from other tenant users. To make your entities multi-tenant aware, all you have to do is to implement the `IMultiTenant` interface provided by the ABP Framework. + +Here, is an example `Product` entity that should support multi-tenancy: + +````csharp +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace MtDemoApp +{ + public class Product : AggregateRoot, IMultiTenant //Implementing the interface + { + public Guid? TenantId { get; set; } //Defined by the IMultiTenant interface + public string Name { get; set; } + public float Price { get; set; } + } +} +```` + +In this way, ABP Framework automatically isolates data using the `TenantId` property. You don't need to care about how to set `TenantId` or filter data when you need to query from database - all automated. + +### Separate Tenant Databases Model + +In the separate tenant database model, each tenant has a dedicated physical database (with a separate connection string), as shown below: + +![separate-tenant-database-multi-tenancy](separate-tenant-database-multi-tenancy.png) + +ABP Framework can automatically select the right database from the current user's tenant context. Again, it is completely automated. You just need to set a connection string for a tenant, as we will do later in this article. + +Even each tenant has a separate database, we still need to a main database to store host-side data, like a table of tenants, their connection strings and some other management data for tenants. Also, tenant-independent (or tenant-shared) application data is stored in the main database. + +### Hybrid Multi-Tenant Database Model + +Lastly, you may want to have a hybrid model, where some tenants shares a single database (they don't have separate databases) but some of them have dedicated databases. In the following figure, Tenant C has its own physical database, but all other tenants data stored in the main database of the application. + +![hybrid-database-multi-tenancy](hybrid-database-multi-tenancy.png) + +ABP Framework handles the complexity: If a tenant has a separate database it uses that tenant's database, otherwise it filters the tenant data by the `TenantId` field in shared tables. + +## Understanding the Separate Tenant Schema Approach + +When you create a new ABP solution, it has a single `DbContext` class (for Entity Framework Core) by default. It also includes the necessary EF Core code-first database migrations to create and update the database. As a result of this approach, the main database schema (tables and their fields) will be identical with a tenant database schema. As a drawback of that, tenant databases have some tables that are not meaningful and not used. For example, Tenants table (a list of tenants) will be created in the tenant database, but will never be used (because tenant list is stored in the main database). + +As a solution to that problem, ABP Studio provides a "Use separate tenant schema" option on the Multi-Tenancy step of the solution creation wizard: + +![separate-tenant-schema-option](separate-tenant-schema-option.png) + +This option is only available for the [Layered Monolith (optionally Modular) Solution Template](https://abp.io/docs/latest/get-started/layered-web-application). We don't provide that option in other templates, because: + +* [Single-Layer](https://abp.io/docs/latest/get-started/single-layer-web-application) template is recommended for more simpler applications with an easy-to-understand architecture. We don't want to add these kind of complications in that template. +* [Microservice](https://abp.io/docs/latest/get-started/microservice) template already has a separate database for each service. Having multiple database schema (and multiple `DbContext` classes) for each service makes it over complicated without bringing much value. + +While you can manually convert your applications so they support separate database schema approach (ABP is flexible), it is not recommended to do it for these solution types. + +> Note that "Separate database per tenant" approach is already supported by default for the Single-Layer template too. "Separate tenant schema" is something different as I explained in this section. + +## Creating a new Application + +Follow the *[Get Started tutorial](https://abp.io/docs/latest/get-started/layered-web-application)* to create a new ABP application. Remember to select the "*Use separate tenant schema*" option since I want to demonstrate it in this article. + +## Understanding the DbContext Structure + +When you open the solution in your IDE, you will see the following structure under the `.EntityFrameworkCore` project: + +![multi-tenancy-dbcontext-structure](multi-tenancy-dbcontext-structure.png) + +There are 3 DbContext-related classes here (MtDemoApp is your application name): + +* `MtDemoAppDbContext` class is used to map entities for the main (host + shared) database. +* `MtDemoAppTenantDbContext` class is used to map entities for tenant that have separate physical databases. +* `MtDemoAppDbContextBase` is an abstract base class for the classes explained above. In this way, you can configure common mapping logic here. + +Let's see these classes a bit closer... + +### The Main `DbContext` Class + +Here the main `DbContext` class: + +````csharp +public class MtDemoAppDbContext : MtDemoAppDbContextBase +{ + public MtDemoAppDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.SetMultiTenancySide(MultiTenancySides.Both); + + base.OnModelCreating(builder); + } +} +```` + +* It inherits from the `MtDemoAppDbContextBase` as I mentioned before. So, any configuration made in the base class is also valid here. +* `OnModelCreating` overrides the base method and sets the multi-tenancy side as `MultiTenancySides.Both`. `Both` means this database can store host data as well as tenant data. This is needed because we store data in this database for the tenants who don't have a separate database. + +### The Tenant `DbContext` class + +Here is the tenant-specific `DbContext` class: + +````csharp +public class MtDemoAppTenantDbContext : MtDemoAppDbContextBase +{ + public MtDemoAppTenantDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.SetMultiTenancySide(MultiTenancySides.Tenant); + + base.OnModelCreating(builder); + } +} +```` + +The only difference is that we used `MultiTenancySides.Tenant` as the multi-tenancy side here, since this `DbContext` will only have entities/tables for tenants that have separate databases. + +### The Base `DbContext` Class + +Here is the base `DbContext` class: + +````csharp +public abstract class MtDemoAppDbContextBase : AbpDbContext + where TDbContext : DbContext +{ + + public MtDemoAppDbContextBase(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureBackgroundJobs(); + builder.ConfigureAuditLogging(); + builder.ConfigureIdentityPro(); + builder.ConfigureOpenIddictPro(); + builder.ConfigureFeatureManagement(); + builder.ConfigureLanguageManagement(); + builder.ConfigureSaas(); + builder.ConfigureTextTemplateManagement(); + builder.ConfigureBlobStoring(); + builder.ConfigureGdpr(); + + /* Configure your own tables/entities inside here */ + + //builder.Entity(b => + //{ + // b.ToTable(MtDemoAppConsts.DbTablePrefix + "YourEntities", MtDemoAppConsts.DbSchema); + // b.ConfigureByConvention(); //auto configure for the base class props + // //... + //}); + + //if (builder.IsHostDatabase()) + //{ + // /* Tip: Configure mappings like that for the entities only + * available in the host side, + // * but should not be in the tenant databases. */ + //} + } +} +```` + +This `DbContext` class configures database mappings for all the [application modules](https://abp.io/docs/latest/modules) used by this application by calling their extension methods, like `builder.ConfigureBackgroundJobs()`. Each of these extension methods are defined as multi-tenancy aware and care about what you've set for the multi-tenancy side. + +### Where to Configure Your Entities? + +You can configure your entity mappings in the `OnModelCreating` method in any of the `DbContext` classes that was explained: + +* If you configure in the main `DbContext` class, these configuration will be valid only for the main database. So, don't configure tenant-related configuration here, otherwise, it won't be applied for the tenants who have separate databases. +* If you configure in the tenant `DbContext` class, it will be valid only for the tenants with separate databases. You rarely need to do that. You typically want to make same configuration in the base `DbContext` to support hybrid scenarios (some tenants use the main (shared) database and some tenants have separate databases). +* If you configure in the base `DbContext` class, it will be valid for the main database and tenant databases. You typically define tenant-related configuration here. That means, if you have a multi-tenant `Product` entity, then you should define its EF Core database mapping configuration here, so the Products table is created in the main database as well as in the tenant databases. + +The recommended approach is to configure all the mapping in the base class, but add controls like `builder.IsHostDatabase()` and `builder.IsTenantDatabase()` to conditionally configure the mappings: + +![builder-check-tenant-side](builder-check-tenant-side.png) + +## Adding Database Migrations + +In this section, I will show how to configure your entity mappings, generate database migrations and apply to the database. + +### Defining an Entity + +Let's define a `Product` entity in the `.Domain` layer of your application: + +````csharp +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace MtDemoApp +{ + public class Product : AggregateRoot, IMultiTenant + { + public Guid? TenantId { get; set; } + public string Name { get; set; } + public float Price { get; set; } + } +} +```` + +### Configuring the Database Mapping + +Open the `MtDemoAppDbContextBase` class and add the following property to the class: + +````csharp +public DbSet Products { get; set; } +```` + +Then add the following mapping code inside the `OnModelCreating` method (after all other existing code): + +````csharp +builder.Entity(b => +{ + b.ToTable(MtDemoAppConsts.DbTablePrefix + "Products", MtDemoAppConsts.DbSchema); + b.ConfigureByConvention(); //auto-configure for the base class props + b.Property(x => x.Name).IsRequired().HasMaxLength(100); +}); +```` + +We made the configuration in the base class since the `Products` table should be created in all databases, not only in the main database. + +>`DbTablePrefix` and `DbSchema` are optional and configurable in your application. You can change or remove them. + +### Add a New Database Migration for the Main Database + +To add a new EF Core database migration, we can use ABP Studio UI or EF Core command-line commands. I will show both of these approaches here. + +#### Using the ABP Studio "Add Migrations" UI + +You can right-click the `.EntityFrameworkCore` package in the ABP Studio's *Solution Explorer* panel and select *EF Core CLI* -> *Add Migration* command as shown below: + +![abp-studio-add-migration](abp-studio-add-migration.png) + +You set a migration name on the opened dialog: + +![abp-studio-add-migration-set-name](abp-studio-add-migration-set-name.png) + +If you select the *Update Database* checkbox it will apply changes to the database after generating the migration code. + +Lastly, select the main DbContext class for this migration: + +![abp-studio-add-migration-select-dbcontext](abp-studio-add-migration-select-dbcontext.png) + +This dialog is shown when your application has multiple `DbContext` classes. Once you click the *OK* button, a new migration class is added under the `Migrations` folder of the `.EntityFrameworkCore` project (you can see in your coding editor): + +![added-product-entity-migration-main-context](added-product-entity-migration-main-context.png) + +Since we selected the *Update Database* option, the database table is also created. The following screenshot shows the `AppProducts` table (`App` is the default prefix for your tables, but you can change or remove it) in Microsoft SQL Server Management Studio: + +![product-database-table](product-database-table.png) + +#### Using a Command-Line Terminal + +If you prefer to use the command-line terminal (instead of ABP Studio UI), open a command-line terminal in the directory of the `.EntityFrameworkCore` project. As a shortcut, you can right-click the `.EntityFrameworkCore` project in ABP Studio, then select *Open with* -> *Terminal* command as shown in the following figure: + +![abp-studio-open-with-terminal](abp-studio-open-with-terminal.png) + +Then you can use the [EF Core command-line tool](https://learn.microsoft.com/en-us/ef/core/cli/dotnet) to add a new database migration: + +````bash +dotnet ef migrations add "Added_Product_Entity" --context MtDemoAppDbContext +```` + +It is important to set the `--context` parameter since we have two DbContext classes in the same project. + +After adding the migration, you can update the database: + +````bash +dotnet ef database update "Added_Product_Entity" --context MtDemoAppDbContext +```` + +> If you are using Visual Studio, you can also use the [Package Manager Console](https://learn.microsoft.com/en-us/ef/core/cli/powershell) inside your IDE to add migrations and update the database. + +### Add a New Database Migration for the Tenant Database + +We added a database migration for the main (shared) database. We also need to add a new database migration for tenants who have separate databases. + +This time, no need to configure the DbContext since we did it in the base DbContext class, so it is valid for both of the DbContext classes. Just right-click the `.EntityFrameworkCore` package in the ABP Studio's *Solution Explorer* panel and select *EF Core CLI* -> *Add Migration* command as shown below: + +![abp-studio-add-migration](abp-studio-add-migration.png) + +You can set the same or a different migration name here: + +![abp-studio-add-migration-set-name](abp-studio-add-migration-set-name.png) + +The important part is to select the Tenant DbContext in the next dialog, because we want to change the tenant database this time: + +![abp-studio-context-selection](abp-studio-context-selection.png) + +After clicking the *OK* button, it will add a new database migration class, but this time to the `TenantMigrations` folder: + +![added-product-entity-migration-tenant-context](added-product-entity-migration-tenant-context.png) + +ABP Studio is smart enough to select the right folder name for the new migration by mapping with the DbContext name. However, you could manually type `TenantMigrations` in the *Output directory* textbox. + +Since we selected the *Update Database* option, it also applied changes to the database. But, which database? Interestingly, it automatically creates a second database for tenants with the project name + `_Tenant` suffix: + +![tenant-database](tenant-database.png) + +> This new database is never used on runtime or production. It is only created to allow you to see the schema (tables and their fields) on development time to be sure that everything is as expected. As you see, some tables (like `Saas*` and `OpenIddict*`) are not available in that database, since they are used on the host side and only necessary to be in the main database. +> +> So, where is the real tenant database? If a tenant's database is dedicated (separate), it is created on runtime as I will explain in the *Managing Tenant Databases and Connection Strings* section later. + +You can see that database's connection string in the `appsettings.development.json` file of the `.DbMigrator` project in the solution. If you want to understand how it works, you can check source code of the `DbContextFactory` classes in the `.EntityFrameworkCore` project: + +![dbcontext-factories](dbcontext-factories.png) + +These factory classes are used to create `DbContext` instances when you execute *Add Migration* and *Update Database* commands. + +## Managing Tenant Databases and Connection Strings + +Until now, we even didn't run the application. It is the time to do it. + +### Running the Application with ABP Studio + +You can run the `.Web` project in your IDE. But I prefer to use ABP Studio's *[Solution Runner](https://abp.io/docs/latest/studio/running-applications)* feature here. You can open the *Solution Runner* panel in *ABP Studio* and click the play icon near to the solution root (`MyDemoApp`): + +![abp-studio-solution-runner](abp-studio-solution-runner.png) + +Once the application runs (and you see the blue link icon near to it), right click and select the *Browse* command: + +![abp-studio-browse](abp-studio-browse.png) + +It will open the application's UI in the built-in browser of ABP Studio. You can Login the application (with `admin` as user name and `1q2w3E*` as the default password) and navigate to the *Saas* -> *Tenants* page. + +### Creating a New Tenant with the Shared Database + +The *Tenants* page of the [SaaS module](https://abp.io/modules/Volo.SaaS) is shown below: + +![abp-saas-tenants-page](abp-saas-tenants-page.png) + +As you see, there is no tenant at the beginning. I can click the *+ New tenant* button to create the first tenant: + +![new-tenant-dialog-1](new-tenant-dialog-1.png) + +On this screen, we can set the base tenant information. If you click the *Database connection strings* tab, you can see the following UI: + +![new-tenant-dialog-conn-string-1](new-tenant-dialog-conn-string-1.png) + +For this first tenant, I will keep it as default and use the shared (main) database for this tenant's data. After clicking the *Save* button, the tenant is created and an initial [data seed](https://abp.io/docs/latest/framework/infrastructure/data-seeding) operation is automatically performed for us. To see an example, you can open the database, show rows of the `AbpUsers` table: + +![users-table-new-tenant](users-table-new-tenant.png) + +As you see, a new `admin` user has been created with a `TenantId`. The first row is the `admin` user of the host side. So, ABP allows to define same user name in different tenants, because their data (users in this example) are completely isolated from each other. + +### Sign in with the new Tenant + +We created a new tenant. In this step, we will sign in with the new tenant's `admin` user to see the application UI by that new tenant. To do that, we should logout from the host admin user first. Click the user name (`admin`) on the top right area of the application and select the *Log out* command: + +![user-logout](user-logout.png) + +Click the *Login* button again, which redirects you to the *Login* page: + +![user-login](user-login.png) + +In this page, click the *switch* button near to the *TENANT* selection area and type `acme` as *Name*: + +![switch-tenant-dialog](switch-tenant-dialog.png) + +Once you click the *Save* button, you are now in the acme tenant's context. You can see it on the *TENANT* selection area: + +![tenant-acme-name](tenant-acme-name.png) + +> This kind of tenant switch feature is very useful in development to quickly change tenants to test your application. However, in production, you typically want to use subdomain/domain names or another mechanism to determine tenants automatically. When you configure domain based resolution, the tenant selection area is automatically disappears from the login page. You can check the [multi-tenant document](https://abp.io/docs/latest/framework/architecture/multi-tenancy) to learn how to configure it. + +After switching to the `acme` tenant, we can use `admin` as user name and the password you set during the tenant creation (I had set it as `1q2w3E*`) to login to the application. + +Here a screenshot from the *Roles* page after signing in as the `acme` tenant's `admin` user: + +![acme-tenant-screen](acme-tenant-screen.png) + +> Notice that each tenant has its own roles, users, permissions, and other data. If you change roles here, it doesn't affect other tenants or the host side. +> +> Also, you can see that there are less menu items compared to host side. For example, tenant management page is not available for tenants as you can expect. + +### Switch Back to the Host Side + +To switch back to the host side to add a new tenant, logout from the application, click the *Login* button again to open the login page and then again click the *switch* button to change the current tenant context: + +![switch-host-side](switch-host-side.png) + +In this dialog, clear the *Name* field and then *Save* the dialog to switch back to the host side. Then you can use the standard `admin` user name with `1q2w3E*` password to login to the application as the host administrator. + +### Creating a New Tenant with a Separate Database + +Finally, we came to the point that we will create a new tenant with a separate, dedicated database. Open the *Tenants* page of the SaaS module and click the *+ New tenant* button: + +![new-tenant-dialog-2](new-tenant-dialog-2.png) + +Just fill these information as you wish, then open the *Database connection strings* tab: + +![new-tenant-dialog-conn-string-2](new-tenant-dialog-conn-string-2.png) + +Uncheck the *Use the shared database* option and set a connection string to the *Default connection string* for this tenant. I used `Server=(LocalDb)\MSSQLLocalDB;Database=MtDemoApp_Volosoft;Trusted_Connection=True;TrustServerCertificate=true` as the connection string value. The database name is `MtDemoApp_Volosoft`. You can Test the connection string to be sure that it is a valid connection string. + +Once you click the *Save* button, the new tenant is created, a new database is created on the fly, all the database migrations are applied and the initial data seed is performed. You can open the SQL Server Management Studio to see the new database: + +![separate-database](separate-database.png) + +You can check the tables (e.g. `AbpUsers`) to see that only this new tenant's data is stored in this database. To test the application, switch to the Volosoft tenant (as like explained in the *Sign in with the new Tenant* section before), create a new role or user and check the database. + +## Migrating Existing Tenant Databases + +In the previous section, we've seen that a tenant database is automatically created on runtime if you set a connection string for that tenant. Also, all the current migrations are automatically applied to the database, so it becomes up to date. + +But what about existing tenant databases when a new migration is added to the application? Maybe you have a few tenants with their separate databases, or you may have thousands of tenants with separate databases. How will you apply database schema changes to all of these databases? + +The startup template comes with a solution to this problem. There is a `.DbMigrator` console application in the solution that is responsible to apply schema (table and their fields) changes to all of the databases in the system (the main database and all the separate tenant databases). It also executes the data seeding if seed data is available. All you need to do is to execute this application on your production environment while deploying a new version of your application (of course, it is also very useful in the development environment). It checks and upgrades all the databases before the new version of your application is deployed. + +Here is the console log screen when I run the `.DbMigrator` application on my development environment: + +![dbmigrator-logs](dbmigrator-logs.png) + +As you can see in the logs, it first migrates for the main (host) database, then migrates the tenant databases one by one. It doesn't make schema migration for the `acme` tenant since it has not a separate database, but uses the main database. + +In brief, when you make changes on your entity classes; + +1) Add a new migration for the main DbContext class as I explained in this article. +2) Add a new migration for the tenant DbContext class as I explained in this article. +3) Run the `.DbMigrator` application in your development environment to ensure all the databases are up to date. +4) When you deploy your application to production or test environments, remember to run the `.DbMigrator` application first, then update your application. Or better, setup a CI/CD pipeline that automates this process. You can run the `.DbMigrator` every time while deploying the application, regardless of whether there is a schema change or not. + +> If you have too many tenants with separate database, then the migration process may take too much time. `.DbMigrator` provides the fundamental solution. But for more advanced scenarios or bigger systems, you can always develop your own solution. Just check the `.DbMigrator` application to understand how it was implemented. All the necessary code located in your solution, so you can easily understand and freely customize. + +## Conclusion + +In this article, I covered two important aspects of multi-tenant application development: + +* How ABP startup templates provide a multi-tenant application setup, so some tenants may store their data in a single (main, shared) database while some others may have their own dedicated database. +* Demonstrate how it can manage database migration process on the fly for multiple databases. + +I started by defining different database models for multi-tenant applications (Single database, separate databases, and hybrid), showed how to create an ABP application that supports hybrid model, explained the DbContext structure that is coming with the solution template, demonstrated how to define entities, create and apply database migrations in such an application. + +I hope this article gives you a good understanding the problem and the solution provided by the ABP Framework. Please write your questions or comments under this article. + +Enjoy coding! :) + +## Further Reading + +* [ABP Multi-Tenancy document](https://abp.io/docs/latest/framework/architecture/multi-tenancy) +* [Multi-Tenancy Architecture with .NET](https://abp.io/architecture/multi-tenancy) \ No newline at end of file diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-saas-tenants-page.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-saas-tenants-page.png new file mode 100644 index 0000000000..9f3a0ce5ca Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-saas-tenants-page.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-select-dbcontext.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-select-dbcontext.png new file mode 100644 index 0000000000..17b89bee80 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-select-dbcontext.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-set-name.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-set-name.png new file mode 100644 index 0000000000..9ff5ea75f4 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration-set-name.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration.png new file mode 100644 index 0000000000..fe59e93230 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-add-migration.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-browse.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-browse.png new file mode 100644 index 0000000000..3134203343 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-browse.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-context-selection.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-context-selection.png new file mode 100644 index 0000000000..dc1a993088 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-context-selection.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-open-with-terminal.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-open-with-terminal.png new file mode 100644 index 0000000000..17dd1fa4dd Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-open-with-terminal.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-solution-runner.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-solution-runner.png new file mode 100644 index 0000000000..a246d01c81 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/abp-studio-solution-runner.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/acme-tenant-screen.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/acme-tenant-screen.png new file mode 100644 index 0000000000..65f7aa5b48 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/acme-tenant-screen.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-main-context.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-main-context.png new file mode 100644 index 0000000000..a727bcea86 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-main-context.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-tenant-context.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-tenant-context.png new file mode 100644 index 0000000000..f77c5e1e75 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/added-product-entity-migration-tenant-context.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png new file mode 100644 index 0000000000..8b913d4292 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/builder-check-tenant-side.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbcontext-factories.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbcontext-factories.png new file mode 100644 index 0000000000..b7f72d9a32 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbcontext-factories.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbmigrator-logs.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbmigrator-logs.png new file mode 100644 index 0000000000..648d9ccaa8 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/dbmigrator-logs.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/hybrid-database-multi-tenancy.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/hybrid-database-multi-tenancy.png new file mode 100644 index 0000000000..45930e7fb9 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/hybrid-database-multi-tenancy.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png new file mode 100644 index 0000000000..5bb7ad8410 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/multi-tenancy-dbcontext-structure.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-1.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-1.png new file mode 100644 index 0000000000..76a2a33087 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-1.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-2.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-2.png new file mode 100644 index 0000000000..614689552e Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-2.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-1.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-1.png new file mode 100644 index 0000000000..5c6aee61d9 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-1.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-2.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-2.png new file mode 100644 index 0000000000..9404e38a26 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/new-tenant-dialog-conn-string-2.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/product-database-table.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/product-database-table.png new file mode 100644 index 0000000000..58ef8463f2 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/product-database-table.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-database.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-database.png new file mode 100644 index 0000000000..1ec6c36ad7 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-database.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-database-multi-tenancy.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-database-multi-tenancy.png new file mode 100644 index 0000000000..69c2bb4940 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-database-multi-tenancy.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-schema-option.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-schema-option.png new file mode 100644 index 0000000000..40c9c42770 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/separate-tenant-schema-option.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/single-shared-database.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/single-shared-database.png new file mode 100644 index 0000000000..4363dc792e Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/single-shared-database.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-host-side.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-host-side.png new file mode 100644 index 0000000000..e4e5f8943d Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-host-side.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-tenant-dialog.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-tenant-dialog.png new file mode 100644 index 0000000000..2828906d1e Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/switch-tenant-dialog.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-acme-name.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-acme-name.png new file mode 100644 index 0000000000..a08537ee4e Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-acme-name.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-database.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-database.png new file mode 100644 index 0000000000..32f7292e3c Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/tenant-database.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-login.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-login.png new file mode 100644 index 0000000000..39cdac5aaf Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-login.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-logout.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-logout.png new file mode 100644 index 0000000000..ae7885fa0e Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/user-logout.png differ diff --git a/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/users-table-new-tenant.png b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/users-table-new-tenant.png new file mode 100644 index 0000000000..0433da73b5 Binary files /dev/null and b/docs/en/Community-Articles/2025-07-26-Separate-Tenant-Schema/users-table-new-tenant.png differ diff --git a/docs/en/Community-Articles/2025-07-31-How-to-build-persistent-background-jobs-with-abp-framework-and-quartz/Post.md b/docs/en/Community-Articles/2025-07-31-How-to-build-persistent-background-jobs-with-abp-framework-and-quartz/Post.md new file mode 100644 index 0000000000..eb69c93908 --- /dev/null +++ b/docs/en/Community-Articles/2025-07-31-How-to-build-persistent-background-jobs-with-abp-framework-and-quartz/Post.md @@ -0,0 +1,548 @@ + +# How to Build Persistent Background Jobs with ABP Framework and Quartz + +## Introduction + +In modern SaaS applications, automated background processing is essential for delivering reliable user experiences. Whether you're sending subscription reminders, processing payments, or generating reports, background jobs ensure critical tasks happen on schedule without blocking your main application flow. + +### What is `Quartz.NET`? + +`Quartz.NET` is a powerful, open-source job scheduling library for .NET applications that provides cron-based scheduling for complex time patterns, job persistence across application restarts, clustering support for high-availability scenarios, flexible trigger types, and the ability to pass parameters to jobs through job data maps. It's the de facto standard for enterprise-grade job scheduling in the .NET ecosystem. + +### Quartz Storage Options: In-Memory vs Persistent + +When configuring **Quartz**, you have two primary storage options, each with significant implications for how your application behaves: + +### 🧠 In-Memory Storage (`RAMJobStore`) +- Keeps all job information in application memory. +- **Very fast** – no database overhead. +- **Volatile** – all jobs, triggers, and schedules are lost when the application stops or restarts. +- Best suited for: + - Development environments. + - Scenarios where job loss is acceptable. + +### 🗃️ Persistent Storage (`JobStoreTX` or similar) +- Stores all job information in a database. +- **Reliable** – schedules persist across: + - Application restarts + - Server crashes + - Deployments +- **Supports horizontal scaling** – multiple application instances can share the same job queue. +- **Slight performance overhead** due to database I/O. +- Best choice for: + - Production systems. + - Any scenario where **business continuity and reliability** are critical. + +### How ABP Simplifies Quartz Integration + +ABP handles Quartz configuration, dependency injection, and lifecycle management automatically. Developers define jobs using `QuartzBackgroundWorkerBase` and access services via `ICachedServiceProvider`, following ABP's standard conventions and leveraging optimal service caching for background job scenarios. + +### Benefits of the Integration + +- Full support for ABP’s cross-cutting concerns (e.g., multi-tenancy, localization) +- Robust scheduling powered by Quartz +- Built-in logging, error handling, and performance monitoring +- Scales easily without modifying business logic + +### Real-World Use Case: Subscription Reminders + +In this tutorial, we'll build a subscription reminder system that monitors client subscriptions, identifies those nearing expiration, sends professional email reminders seven days before expiration, tracks reminder history to prevent duplicates, and runs automatically every day at 9:00 AM using Quartz scheduling with PostgreSQL persistence. This system demonstrates how ABP and Quartz work together to solve real business problems with clean, maintainable code that follows enterprise-grade patterns. + +## Installing and Configuring Quartz + +Getting Quartz up and running in an ABP application is straightforward thanks to ABP's dedicated integration package. We'll replace the default background job system with Quartz for persistent job storage and robust scheduling capabilities. + +### Adding the Quartz Package + +The easiest way to add Quartz support to your ABP application is using the ABP CLI. Open a terminal in your project directory and run: + +```bash +abp add-package Volo.Abp.BackgroundWorkers.Quartz +``` + +This command automatically adds the necessary NuGet package reference and updates your module dependencies. The ABP CLI handles all the heavy lifting, ensuring you get the correct version that matches your ABP Framework version. + +### Configuring Quartz for Persistent Storage + +Once the package is installed, you need to configure Quartz to use your database (in my case it is PostgreSQL) for job persistence. This configuration goes in your main module's `PreConfigureServices` method: + +```csharp +[DependsOn( + // ... other dependencies + typeof(AbpBackgroundJobsQuartzModule), + typeof(AbpBackgroundWorkersQuartzModule) +)] +public class MySaaSApplicationModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + ConfigureAuthentication(context, configuration); + ConfigureUrls(configuration); + ConfigureImpersonation(context, configuration); + ConfigureQuartz(); // Add this line + } + + private void ConfigureQuartz() + { + PreConfigure(options => + { + options.Properties = new NameValueCollection + { + ["quartz.scheduler.instanceName"] = "QuartzScheduler", + ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", + ["quartz.jobStore.tablePrefix"] = "qrtz_", + ["quartz.jobStore.dataSource"] = "myDS", + ["quartz.dataSource.myDS.connectionString"] = _configuration.GetConnectionString("Default"), + ["quartz.dataSource.myDS.provider"] = "Npgsql", + ["quartz.serializer.type"] = "json" + }; + }); + } +} +``` + +This configuration tells Quartz to store all job information in your PostgreSQL database using tables prefixed with "qrtz_". The key points are: + +- **Job Store Type**: Uses ADO.NET with transaction support for reliable job persistence +- **Connection String**: Shares your application's existing database connection +- **Table Prefix**: Keeps Quartz tables separate with the "qrtz_" prefix +- **JSON Serialization**: Makes job data readable and debuggable +- **PostgreSQL Provider**: Uses Npgsql for optimal PostgreSQL integration + +When your application starts, ABP automatically initializes the Quartz scheduler with these settings. Any background workers you create will be registered and scheduled automatically, with their state persisted to the database for reliability across application restarts. + +For detailed installation options and advanced configuration scenarios, check the official [ABP documentation.](https://abp.io/docs/latest/framework/infrastructure/background-workers/quartz) + + +## Database Setup for Quartz + +With Quartz configured for persistent storage, we need to create the necessary database tables where Quartz will store job definitions, triggers, and execution history. Rather than running SQL scripts directly against the database, we'll use Entity Framework migrations to maintain consistency with ABP's database management approach. + +### Creating an Empty Migration for Quartz Tables + +Instead of executing raw SQL scripts against the database, we created an empty Entity Framework migration and populated it with the required Quartz table definitions. This approach keeps all database changes within the migration system, ensuring they're version-controlled, repeatable, and consistent across different environments. + +To create the empty migration, we used the standard Entity Framework CLI command: + +```bash +dotnet ef migrations add AddQuartzTables +``` + +This generates a new migration file with empty `Up` and `Down` methods that we can populate with the Quartz table creation scripts. + +### Adding Quartz SQL Schema to Migration + +Once the empty migration was created, we populated it with the PostgreSQL-specific SQL needed to create all Quartz tables. The SQL scripts were obtained from the official Quartz repository, which provides database schema scripts for various database providers: + +```csharp +public partial class AddQuartzTables : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + CREATE TABLE qrtz_job_details ( + sched_name VARCHAR(120) NOT NULL, + job_name VARCHAR(200) NOT NULL, + job_group VARCHAR(200) NOT NULL, + description VARCHAR(250) NULL, + job_class_name VARCHAR(250) NOT NULL, + is_durable BOOLEAN NOT NULL, + is_nonconcurrent BOOLEAN NOT NULL, + is_update_data BOOLEAN NOT NULL, + requests_recovery BOOLEAN NOT NULL, + job_data BYTEA NULL, + PRIMARY KEY (sched_name, job_name, job_group) + ); + + CREATE TABLE qrtz_triggers ( + sched_name VARCHAR(120) NOT NULL, + trigger_name VARCHAR(200) NOT NULL, + trigger_group VARCHAR(200) NOT NULL, + job_name VARCHAR(200) NOT NULL, + job_group VARCHAR(200) NOT NULL, + -- ... additional columns and constraints + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, job_name, job_group) REFERENCES qrtz_job_details(sched_name, job_name, job_group) + ); + + -- Additional tables: qrtz_simple_triggers, qrtz_cron_triggers, + -- qrtz_simprop_triggers, qrtz_blob_triggers, qrtz_calendars, + -- qrtz_paused_trigger_grps, qrtz_fired_triggers, qrtz_scheduler_state, qrtz_locks + "); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DROP TABLE IF EXISTS qrtz_locks; + DROP TABLE IF EXISTS qrtz_scheduler_state; + -- ... drop all other Quartz tables in reverse order + DROP TABLE IF EXISTS qrtz_triggers; + DROP TABLE IF EXISTS qrtz_job_details; + "); + } +} +``` + +The complete SQL scripts for all supported database providers, including PostgreSQL, MySQL, SQL Server, and others, can be found in the official `Quartz.NET` repository. You should use the script that matches your specific database provider and version requirements. + +### Why Use Migrations Instead of Direct SQL Scripts? + +This migration-based approach offers several important advantages over running SQL scripts directly: + +**Version Control Integration**: The migration becomes part of your codebase, tracked in source control alongside your application changes. This means every developer and deployment environment gets the exact same database schema. + +**Rollback Capability**: The `Down` method provides a clean way to remove Quartz tables if needed, something that's much harder to manage with standalone SQL scripts. + +**Environment Consistency**: Whether you're setting up a development machine, staging server, or production deployment, running DBMigrator or `dotnet ef database update` command ensures the same schema is created everywhere. + +**Integration with ABP's Database Management**: This approach aligns perfectly with how ABP manages all other database changes, keeping your database evolution strategy consistent. + +The Quartz tables created by this migration handle all aspects of job persistence - from storing job definitions and triggers to tracking execution history and managing scheduler state. With these tables in place, your Quartz scheduler can reliably persist jobs across application restarts and coordinate work across multiple application instances if needed. + +After creating this migration, running DBMigrator `dotnet ef database update` will create all the necessary Quartz infrastructure in your PostgreSQL database, ready to store and manage your background jobs. + +For complete SQL scripts for your specific database provider, visit the official [Quartz documentation.](https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html#creating-and-initializing-database) +## Building the Business Logic + +Before implementing our Quartz background job, we needed to create the essential business entities and services that our subscription reminder system would work with. Since this article focuses on Quartz integration rather than general ABP development patterns, we'll keep this section brief and move quickly to the background job implementation. + +### Core Entities and Services + +For our subscription reminder system, we created the following core components: + +**Entities:** +- **`Client`**: Represents customers with subscription information (Name, Email, SubscriptionEnd, IsActive) +- **`ReminderLog`**: Tracks when reminder emails have been sent to prevent duplicates + +**Application Services:** +- **`ClientAppService`**: Handles CRUD operations and provides methods to find clients with expiring subscriptions +- **`ReminderLogAppService`**: Manages reminder history and prevents duplicate notifications +- **`EmailService`**: Sends professional HTML reminder emails via SMTP + +**Data Transfer Objects (DTOs):** +- Complete set of DTOs for both entities following ABP conventions +- Input/output DTOs for all service operations + +### Business Logic Overview + +The system follows standard ABP patterns with entities inheriting from `FullAuditedAggregateRoot`, services implementing `ICrudAppService` interfaces, and proper AutoMapper configurations for entity-DTO mapping. We also included a data seeder to create sample clients for testing purposes. + +The key business methods our background job will use are: +- `GetExpiringClientsAsync()` - Finds clients whose subscriptions expire in the next 7 days +- `CreateAsync()` - Logs when a reminder has been sent +- `SendSubscriptionExpiryReminderAsync()` - Sends professional email reminders + +### Focus on Background Operations + +Rather than diving deep into ABP entity creation, repository patterns, or service layer implementation details, we'll move directly to the heart of this article: implementing robust background jobs with Quartz. The entities and services we created simply provide the business context for our background job to operate within. + +The real value of this tutorial lies in showing how ABP's `QuartzBackgroundWorkerBase` integrates seamlessly with your business logic to create reliable, persistent background operations that survive application restarts and scale across multiple instances. + +Let's now implement the background job that ties everything together and demonstrates the power of ABP + Quartz integration. + + +## Implementing the Background Job (The ABP Way) + +This is where the magic happens. ABP's integration with Quartz provides a clean, powerful way to create background jobs that follow framework conventions while leveraging Quartz's robust scheduling capabilities. Let's dive into how we implemented our subscription reminder job and explore the advanced features ABP provides. + +### Creating a QuartzBackgroundWorkerBase Job + +Instead of implementing Quartz's raw `IJob` interface, ABP provides `QuartzBackgroundWorkerBase`, which integrates seamlessly with ABP's dependency injection, logging, and lifecycle management systems: + +```csharp +public class SubscriptionExpiryNotifierJob : QuartzBackgroundWorkerBase +{ + public SubscriptionExpiryNotifierJob() + { + // Configure the job to run daily at 9:00 AM + JobDetail = JobBuilder.Create() + .WithIdentity(nameof(SubscriptionExpiryNotifierJob)) + .Build(); + + Trigger = TriggerBuilder.Create() + .WithIdentity(nameof(SubscriptionExpiryNotifierJob)) + .WithCronSchedule("0 0 9 * * ?") // Every day at 9:00 AM + .Build(); + + ScheduleJob = async scheduler => + { + if (!await scheduler.CheckExists(JobDetail.Key)) + { + await scheduler.ScheduleJob(JobDetail, Trigger); + } + }; + } + + public override async Task Execute(IJobExecutionContext context) + { + // Use ICachedServiceProvider for better performance and proper scoping + var serviceProvider = ServiceProvider.GetRequiredService(); + + // These services will be cached and reused throughout the job execution + var clientAppService = serviceProvider.GetRequiredService(); + var reminderLogAppService = serviceProvider.GetRequiredService(); + var emailService = serviceProvider.GetRequiredService(); + + Logger.LogInformation("🔄 Starting subscription expiry notification job..."); + + // 1. Get clients expiring in 7 days + var expiringClients = await clientAppService.GetExpiringClientsAsync(7); + + Logger.LogInformation("📋 Found {Count} clients with expiring subscriptions", expiringClients.Count); + + // 2. Process each client + foreach (var client in expiringClients) + { + await ProcessClientAsync(client, emailService, reminderLogAppService); + } + + Logger.LogInformation("✅ Job completed successfully"); + } +} +``` + +### Key Implementation Features + +**Constructor-Based Configuration**: Unlike traditional Quartz jobs that require external scheduling code, ABP's approach lets you define both the job and its schedule directly in the constructor. This keeps related configuration together and makes the job self-contained. + +**ABP Service Integration**: The `ICachedServiceProvider` gives you access to any service in ABP's dependency injection container, enabling you to use application services, repositories, domain services, or any other ABP component with optimized caching and proper scoping. + +**Built-in Logging**: The `Logger` property provides access to ABP's logging infrastructure, automatically including context like correlation IDs and tenant information in multi-tenant applications. + +**Custom Scheduling Logic**: The `ScheduleJob` property allows you to customize how the job gets registered with Quartz. In our example, we check if the job already exists before scheduling it, preventing duplicate registrations during application restarts. + +### Understanding Quartz Trigger Types + +Quartz provides several trigger types to handle different scheduling requirements. Choosing the right trigger type is crucial for your job's behavior and performance. + +#### CronTrigger - Complex Time-Based Scheduling + +CronTrigger uses cron expressions for sophisticated scheduling patterns. This is what we used for our daily subscription reminders: + +```csharp +// Daily at 9:00 AM +Trigger = TriggerBuilder.Create() + .WithIdentity("DailyReminder") + .WithCronSchedule("0 0 9 * * ?") + .Build(); + +// Every weekday at 2:30 PM +Trigger = TriggerBuilder.Create() + .WithIdentity("WeekdayReport") + .WithCronSchedule("0 30 14 ? * MON-FRI") + .Build(); + +// First day of every month at midnight +Trigger = TriggerBuilder.Create() + .WithIdentity("MonthlyCleanup") + .WithCronSchedule("0 0 0 1 * ?") + .Build(); +``` + +**Cron Expression Format**: `Seconds Minutes Hours Day-of-Month Month Day-of-Week Year(optional)` +- `0 0 9 * * ?` = 9:00 AM every day +- `0 */15 * * * ?` = Every 15 minutes +- `0 0 12 ? * SUN` = Every Sunday at noon + +#### SimpleTrigger - Interval-Based Scheduling + +SimpleTrigger is perfect for jobs that need to run at regular intervals or a specific number of times: + +```csharp +// Run every 30 seconds indefinitely +Trigger = TriggerBuilder.Create() + .WithIdentity("HealthCheck") + .StartNow() + .WithSimpleSchedule(x => x + .WithIntervalInSeconds(30) + .RepeatForever()) + .Build(); + +// Run every 5 minutes, but only 10 times +Trigger = TriggerBuilder.Create() + .WithIdentity("LimitedRetry") + .StartNow() + .WithSimpleSchedule(x => x + .WithIntervalInMinutes(5) + .WithRepeatCount(9)) // 0-based, so 9 = 10 executions + .Build(); + +// One-time execution after 1 hour delay +Trigger = TriggerBuilder.Create() + .WithIdentity("DelayedCleanup") + .StartAt(DateTimeOffset.UtcNow.AddHours(1)) + .Build(); +``` + +#### CalendarIntervalTrigger - Calendar-Aware Intervals + +CalendarIntervalTrigger handles intervals that need to respect calendar boundaries: + +```csharp +// Every month on the same day (handles varying month lengths) +Trigger = TriggerBuilder.Create() + .WithIdentity("MonthlyBilling") + .WithCalendarIntervalSchedule(x => x + .WithIntervalInMonths(1)) + .Build(); + +// Every week, starting Monday +Trigger = TriggerBuilder.Create() + .WithIdentity("WeeklyReport") + .WithCalendarIntervalSchedule(x => x + .WithIntervalInWeeks(1)) + .Build(); +``` + +#### DailyTimeIntervalTrigger - Time Windows + +DailyTimeIntervalTrigger runs jobs within specific time windows on certain days: + +```csharp +// Every 2 hours between 8 AM and 6 PM, Monday through Friday +Trigger = TriggerBuilder.Create() + .WithIdentity("BusinessHoursSync") + .WithDailyTimeIntervalSchedule(x => x + .OnMondayThroughFriday() + .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 0)) + .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(18, 0)) + .WithIntervalInHours(2)) + .Build(); +``` + +### Choosing the Right Trigger Type + +For different scenarios, you'd choose different trigger types: + +- **Daily/Weekly/Monthly Operations**: Use **CronTrigger** for maximum flexibility +- **High-Frequency Tasks**: Use **SimpleTrigger** for performance (every few seconds/minutes) +- **Business Calendar Operations**: Use **CalendarIntervalTrigger** for month-end reports, quarterly tasks +- **Business Hours Operations**: Use **DailyTimeIntervalTrigger** for operations that should only run during specific hours + +### Automatic Job Registration + +One of ABP's most powerful features is automatic job discovery and registration. When your application starts, ABP automatically: + +1. **Scans for Background Workers**: ABP discovers all classes inheriting from `QuartzBackgroundWorkerBase` +2. **Registers with DI Container**: Each job is registered as a service in the dependency injection container +3. **Schedules with Quartz**: ABP calls the `ScheduleJob` delegate to register the job with the Quartz scheduler +4. **Handles Lifecycle**: ABP manages starting and stopping jobs with the application lifecycle + +This means you simply create your job class, and ABP handles everything else. No manual registration, no startup code, no configuration files - it just works. + +### Understanding Misfire Handling + +Misfires occur when a scheduled job cannot execute at its intended time, typically due to system downtime, resource constraints, or the scheduler being paused. Quartz provides several misfire instructions to handle these scenarios: + +#### CronTrigger Misfire Instructions + +For cron-based schedules like our daily reminder job, Quartz offers these misfire behaviors: + +**`MisfireInstruction.DoNothing`** (Default): +```csharp +Trigger = TriggerBuilder.Create() + .WithIdentity(nameof(SubscriptionExpiryNotifierJob)) + .WithCronSchedule("0 0 9 * * ?", x => x.WithMisfireHandlingInstructionDoNothing()) + .Build(); +``` +- Skips all missed executions +- Waits for the next naturally scheduled time +- Best for jobs where missing executions is acceptable + +**`MisfireInstruction.FireOnceNow`**: +```csharp +.WithCronSchedule("0 0 9 * * ?", x => x.WithMisfireHandlingInstructionFireAndProceed()) +``` +- Immediately executes one missed job upon recovery +- Then continues with the normal schedule +- Useful when you need to catch up on missed work + +**`MisfireInstruction.IgnoreMisfires`**: +```csharp +.WithCronSchedule("0 0 9 * * ?", x => x.WithMisfireHandlingInstructionIgnoreMisfires()) +``` +- Executes all missed jobs immediately upon recovery +- Can cause a burst of executions after extended downtime +- Use carefully to avoid overwhelming the system + +#### SimpleTrigger Misfire Instructions + +Simple triggers have their own set of misfire behaviors: + +**`MisfireInstruction.FireNow`**: Execute immediately when recovered +**`MisfireInstruction.RescheduleNowWithExistingRepeatCount`**: Start over with remaining repeat count +**`MisfireInstruction.RescheduleNowWithRemainingRepeatCount`**: Continue as if no misfire occurred +**`MisfireInstruction.RescheduleNextWithExistingCount`**: Wait for next interval, keep original repeat count + +### Real-World Misfire Considerations + +For our subscription reminder system, we chose the default `DoNothing` behavior because: + +- **Business Logic**: Sending yesterday's reminder today might confuse customers +- **Duplicate Prevention**: Our job checks for existing reminders, so running late won't cause duplicate emails +- **Resource Management**: We avoid overwhelming the email system after extended downtime + +However, for other scenarios you might choose differently: +- **Financial reporting**: Use `FireOnceNow` to ensure reports are always generated +- **Data synchronization**: Use `IgnoreMisfires` to process all missed sync operations +- **Cache warming**: Use `DoNothing` since stale cache warming provides no value + +### Advanced Job Features + +**Error Handling and Resilience**: Our job implementation includes comprehensive error handling for individual client processing, ensuring one failed email doesn't stop the entire batch: + +```csharp +try +{ + await emailService.SendSubscriptionExpiryReminderAsync(/*...*/); + await LogReminderAsync(client.Id, client.SubscriptionEnd, "Email sent successfully", reminderLogAppService); +} +catch (Exception ex) +{ + Logger.LogError(ex, "❌ Failed to send reminder to {ClientName}", client.Name); + await LogReminderAsync(client.Id, client.SubscriptionEnd, $"Failed: {ex.Message}", reminderLogAppService); +} +``` + +**Duplicate Prevention**: The job checks for existing reminders to prevent sending multiple emails on the same day, even if the job runs multiple times: + +```csharp +private async Task AlreadySentTodayAsync(Guid clientId, IReminderLogAppService reminderLogAppService) +{ + var todayReminders = await reminderLogAppService.GetByClientIdAsync(clientId); + var today = DateTime.UtcNow.Date; + + return todayReminders.Any(r => r.ReminderDate.Date == today); +} +``` + +This implementation demonstrates how ABP's `QuartzBackgroundWorkerBase` provides a clean, powerful foundation for building robust background jobs that integrate seamlessly with your business logic while leveraging Quartz's enterprise-grade scheduling capabilities. + +## Conclusion + +You've successfully built a production-ready subscription reminder system that demonstrates the powerful synergy between ABP Framework and `Quartz.NET`. This isn't just a tutorial example - it's a robust, enterprise-grade solution that handles real business requirements. + +### What We Accomplished + +**✅ Enterprise-Grade Reliability**: PostgreSQL persistence ensures jobs survive restarts and deployments +**✅ ABP Best Practices**: Used `QuartzBackgroundWorkerBase`, `ICachedServiceProvider`, and ABP's logging infrastructure +**✅ Real Business Value**: Automated subscription reminders with duplicate prevention and audit logging +**✅ Flexible Scheduling**: Explored cron expressions, trigger types, and misfire handling strategies + +### The Power of ABP + Quartz Integration + +The combination delivers exceptional value through automatic job discovery, persistent scheduling, built-in dependency injection, and seamless framework integration. You get enterprise reliability with developer-friendly simplicity. + +### Final Thoughts + +Complex background processing doesn't have to be complicated to implement. ABP's thoughtful abstractions combined with Quartz's proven engine create a development experience that's both powerful and enjoyable. + +Whether you're building subscription management, financial reporting, or data synchronization, these patterns provide a solid foundation for reliable, maintainable solutions. + +You can reach sample project's source code from [here](https://github.com/MansurBesleney/MySaaSApplication) + +**Happy coding, and may your background jobs never miss a beat!** 🚀 diff --git a/docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/integration-services.jpeg b/docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/integration-services.jpeg new file mode 100644 index 0000000000..19b1392843 Binary files /dev/null and b/docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/integration-services.jpeg differ diff --git a/docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/post.md b/docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/post.md new file mode 100644 index 0000000000..0e11c505da --- /dev/null +++ b/docs/en/Community-Articles/2025-08-12-Integration-Services-Explained/post.md @@ -0,0 +1,138 @@ +# Integration Services in ABP — What they are, when to use them, and how they behave 🚦 + +If you’ve been building with ABP for a while, you’ve probably used Application Services for your UI and APIs in your .NET and ASP.NET Core apps. Integration Services are similar—but with a different mission: they exist for service-to-service or module-to-module communication, not for end users. + +If you want the formal spec, see the official doc: [Integration Services](../../framework/api-development/integration-services.md). This post is the practical, no-fluff guide. + +## What is an Integration Service? + +An Integration Service is an application service or ASP.NET Core MVC controller marked with the `[IntegrationService]` attribute. That marker tells ABP “this endpoint is for internal communication.” + +- They are not exposed by default (safer for reusable modules and monoliths). +- When exposed, their route prefix is `/integration-api` (so you can easily protect them at your gateway or firewall). +- Auditing is disabled by default for them (less noise for machine-to-machine calls). + +Quick look: + +```csharp +[IntegrationService] +public interface IProductIntegrationService : IApplicationService +{ + Task> GetProductsByIdsAsync(List ids); +} + +public class ProductIntegrationService : ApplicationService, IProductIntegrationService +{ + public Task> GetProductsByIdsAsync(List ids) + { + // fetch and return minimal product info for other services/modules + } +} +``` + +## Are they HTTP endpoints? + +- By default: no (they won’t be reachable over HTTP in the ASP.NET Core routing pipeline). +- If you need them over HTTP (typically for microservices), explicitly enable: + +```csharp +Configure(options => +{ + options.ExposeIntegrationServices = true; +}); +``` + +Once exposed, ABP puts them under `/integration-api/...` instead of `/api/...` in the ASP.NET Core routing pipeline. That’s your hint to restrict them from public internet access. + +## Enable auditing (optional) + +If you want audit logs for integration calls, enable it explicitly: + +```csharp +Configure(options => +{ + options.IsEnabledForIntegrationServices = true; +}); +``` + +## When should you use Integration Services? + +- Internal, synchronous operations between services or modules. +- You need a “thin” API designed for other services (not for UI): minimal DTOs, no view concerns, predictable contracts. +- You want to hide these endpoints from public clients, or only allow them inside your private network or k8s cluster. +- You’re packaging a reusable module that might be used in both monolith and microservice deployments. + +## When NOT to use them + +- Public APIs or anything intended for browsers/mobile apps → use regular application services/controllers. +- Asynchronous cross-service workflows → consider domain events + outbox/inbox; use Integration Services for sync calls. +- Complex, chatty UI endpoints → those belong to your external API surface, not internal integration. + +## Common use-cases and examples + +- Identity lookups across services: an Ordering service needs basic user info from the Identity service. +- Permission checks from another module: a CMS module asks a Permission service for access decisions. +- Product data hydrations: a Cart service needs minimal product details (price, name) from Catalog. +- Internal admin/maintenance operations that aren’t meant for end users but are needed by other services. + +## Example: microservice-to-microservice call + +1) Mark and expose the integration service in the target service: + +```csharp +[IntegrationService] +public interface IUserIntegrationService : IApplicationService +{ + Task FindByIdAsync(Guid id); +} + +Configure(o => o.ExposeIntegrationServices = true); +``` + +2) In the caller service, add an HTTP client proxy only for Integration Services if you like to keep things clean: + +```csharp +services.AddHttpClientProxies( + typeof(TargetServiceApplicationModule).Assembly, + remoteServiceConfigurationName: "TargetService", + asDefaultServices: true, + applicationServiceTypes: ApplicationServiceTypes.IntegrationServices); +``` + +3) Call it just like a local service (ABP’s HTTP proxy handles the wire): + +```csharp +public class OrderAppService : ApplicationService +{ + private readonly IUserIntegrationService _userIntegrationService; + + public OrderAppService(IUserIntegrationService userIntegrationService) + { + _userIntegrationService = userIntegrationService; + } + + public async Task PlaceOrderAsync(CreateOrderDto input) + { + var user = await _userIntegrationService.FindByIdAsync(CurrentUser.GetId()); + // validate user status, continue placing order... + } +} +``` + +## Monolith vs. Microservices + +- Monolith: keep them unexposed and call via DI in-process. You get the same clear contract with zero network overhead. +- Microservices: expose them and route behind your gateway. The `/integration-api` prefix makes it easy to firewall/gateway-restrict. + +## Practical tips + +- Keep integration DTOs lean and stable. These are machine contracts—don’t mix UI concerns. +- Name them clearly (e.g., `UserIntegrationService`) so intent is obvious. +- Guard your ASP.NET Core gateway application: block `/integration-api/*` from public traffic. +- Enable auditing only if you truly need the logs for these calls. + +## Further reading + +- Official docs: [Integration Services](../../framework/api-development/integration-services.md) + +That’s it! Integration Services give you a clean, intentional way to design internal APIs—great in monoliths, essential in microservices. 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 7a5017b369..1bc151bddf 100644 --- a/docs/en/framework/architecture/multi-tenancy/index.md +++ b/docs/en/framework/architecture/multi-tenancy/index.md @@ -382,6 +382,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/fundamentals/object-extensions.md b/docs/en/framework/fundamentals/object-extensions.md index b12be63dc0..87ece27f75 100644 --- a/docs/en/framework/fundamentals/object-extensions.md +++ b/docs/en/framework/fundamentals/object-extensions.md @@ -394,6 +394,22 @@ public class MyProfile : Profile It has the same parameters with the `MapExtraPropertiesTo` method. +#### Mapperly Integration + +If you're using the [Mapperly](https://github.com/riok/mapperly) library, the ABP also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above. + +You can use the `MapExtraProperties` attribute to Mapperly class: + +````csharp +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToProfileDtoMapper : MapperBase +{ + public override partial IdentityUserDto Map(IdentityUser source); + public override partial void Map(IdentityUser source, IdentityUserDto destination); +} +```` + ## Entity Framework Core Database Mapping If you're using the EF Core, you can map an extra property to a table field in the database. Example: 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/docs/en/framework/infrastructure/object-to-object-mapping.md b/docs/en/framework/infrastructure/object-to-object-mapping.md index 8961d804c7..11252ba137 100644 --- a/docs/en/framework/infrastructure/object-to-object-mapping.md +++ b/docs/en/framework/infrastructure/object-to-object-mapping.md @@ -84,7 +84,7 @@ public class UserAppService : ApplicationService } ```` -You should have defined the mappings before to be able to map objects. See the AutoMapper integration section to learn how to define mappings. +You should have defined the mappings before to be able to map objects. See the AutoMapper/Mapperly integration section to learn how to define mappings. ## AutoMapper Integration @@ -217,13 +217,279 @@ public class MyProfile : Profile } ```` +## Mapperly Integration + +[Mapperly](https://github.com/riok/mapperly) is a .NET source generator for generating object mappings. [Volo.Abp.Mapperly](https://www.nuget.org/packages/Volo.Abp.Mapperly) package defines the Mapperly integration for the `IObjectMapper`. + +Once you define mappings class as below, you can use the `IObjectMapper` interface just like explained before. + +### Define Mapping Classes + +You can define a mapper class by using the `Mapper` attribute. The class and methods must be `partial` to allow the Mapperly to generate the implementation during the build process: + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : MapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); +} +```` + +If you also want to map `UserDto` to `User`, you can inherit from the `TwoWayMapperBase` class: + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : TwoWayMapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); + + public override partial User ReverseMap(UserDto destination); + public override partial void ReverseMap(UserDto destination, User source); +} +```` + +### Before and After Mapping Methods + +The base class provides `BeforeMap` and `AfterMap` methods that can be overridden to perform actions before and after the mapping: + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : TwoWayMapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); + + public override partial void BeforeMap(User source) + { + //TODO: Perform actions before the mapping + } + + public override partial void AfterMap(User source, UserDto destination) + { + //TODO: Perform actions after the mapping + } + + public override partial User ReverseMap(UserDto destination); + public override partial void ReverseMap(UserDto destination, User source); + + public override partial void BeforeReverseMap(UserDto destination) + { + //TODO: Perform actions before the reverse mapping + } + + public override partial void AfterReverseMap(UserDto destination, User source) + { + //TODO: Perform actions after the reverse mapping + } +} +```` + +### Mapping the Object Extensions + +[Object extension system](../fundamentals/object-extensions.md) allows to define extra properties for existing classes. ABP provides a mapping definition extension to properly map extra properties of two objects: + +````csharp +[Mapper] +[MapExtraProperties] +public partial class UserToUserDtoMapper : MapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); +} +```` + +It is suggested to use the `MapExtraPropertiesAttribute` attribute if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](../fundamentals/object-extensions.md) for more. + +### Lists and Arrays Support + +ABP Mapperly integration also supports mapping lists and arrays as explained in the [IObjectMapper Interface](#iobjectmappertsource-tdestination-interface) section. + +**Example**: + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : MapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); +} + +var users = await _userRepository.GetListAsync(); // returns List +var dtos = ObjectMapper.Map, List>(users); // creates List +```` + +### Nested Mapping + +When working with nested object mapping, there's an important limitation to be aware of. If you have separate mappers for nested types like in the example below, the parent mapper (`SourceTypeToDestinationTypeMapper`) will not automatically use the nested mapper (`SourceNestedTypeToDestinationNestedTypeMapper`) to handle the mapping of nested properties. This means that configurations like the `MapperIgnoreTarget` attribute on the nested mapper will be ignored during the parent mapping operation. + +````csharp +public class SourceNestedType +{ + public string Name { get; set; } + + public string Ignored { get; set; } +} + +public class SourceType +{ + public string Name { get; set; } + + public SourceNestedType Nested { get; set; } +} + +public class DestinationNestedType +{ + public string Name { get; set; } + + public string Ignored { get; set; } +} + +public class DestinationType +{ + public string Name { get; set; } + + public DestinationNestedType Nested { get; set; } +} + +[Mapper] +public partial class SourceTypeToDestinationTypeMapper : MapperBase +{ + public override partial DestinationType Map(SourceType source); + public override partial void Map(SourceType source, DestinationType destination); +} + +[Mapper] +public partial class SourceNestedTypeToDestinationNestedTypeMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public override partial DestinationNestedType Map(SourceNestedType source); + + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public override partial void Map(SourceNestedType source, DestinationNestedType destination); +} +```` + +There are several ways to solve this nested mapping issue. Choose the approach that best fits your specific requirements: + +#### Solution 1: Multi-Interface Implementation + +Implement both mapping interfaces (`IAbpMapperlyMapper` and `IAbpMapperlyMapper`) in a single mapper class. This approach consolidates all related mapping logic into one class. + +**Important:** Remember to implement `ITransientDependency` to register the mapper class with the dependency injection container. + +````csharp +[Mapper] +public partial class SourceTypeToDestinationTypeMapper : IAbpMapperlyMapper, IAbpMapperlyMapper, ITransientDependency +{ + public partial DestinationType Map(SourceType source); + public partial void Map(SourceType source, DestinationType destination); + public void BeforeMap(SourceType source) + { + } + + public void AfterMap(SourceType source, DestinationType destination) + { + } + + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public partial DestinationNestedType Map(SourceNestedType source); + + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public partial void Map(SourceNestedType source, DestinationNestedType destination); + + public void BeforeMap(SourceNestedType source) + { + } + + public void AfterMap(SourceNestedType source, DestinationNestedType destination) + { + } +} +```` + +#### Solution 2: Consolidate Mapping Methods + +Copy the nested mapping methods from `SourceNestedTypeToDestinationNestedTypeMapper` to the parent `SourceTypeToDestinationTypeMapper` class. This ensures all mapping logic is contained within a single mapper. + +Example: + +````csharp +[Mapper] +public partial class SourceTypeToDestinationTypeMapper : MapperBase +{ + public override partial DestinationType Map(SourceType source); + public override partial void Map(SourceType source, DestinationType destination); + + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public override partial DestinationNestedType Map(SourceNestedType source); + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public override partial void Map(SourceNestedType source, DestinationNestedType destination); +} + +[Mapper] +public partial class SourceNestedTypeToDestinationNestedTypeMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public override partial DestinationNestedType Map(SourceNestedType source); + + [MapperIgnoreTarget(nameof(SourceNestedType.Ignored))] + public override partial void Map(SourceNestedType source, DestinationNestedType destination); +} +```` + +#### Solution 3: Dependency Injection Approach + +Inject the nested mapper as a dependency into the parent mapper and use it in the `AfterMap` method to handle nested object mapping manually. + +Example: + +````csharp +[Mapper] +public partial class SourceTypeToDestinationTypeMapper : MapperBase +{ + private readonly SourceNestedTypeToDestinationNestedTypeMapper _sourceNestedTypeToDestinationNestedTypeMapper; + + public SourceTypeToDestinationTypeMapper(SourceNestedTypeToDestinationNestedTypeMapper sourceNestedTypeToDestinationNestedTypeMapper) + { + _sourceNestedTypeToDestinationNestedTypeMapper = sourceNestedTypeToDestinationNestedTypeMapper; + } + + public override partial DestinationType Map(SourceType source); + public override partial void Map(SourceType source, DestinationType destination); + + public override void AfterMap(SourceType source, DestinationType destination) + { + if (source.Nested != null) + { + destination.Nested = _sourceNestedTypeToDestinationNestedTypeMapper.Map(source.Nested); + } + } +} +```` + +#### Choosing the Right Solution + +Each solution has its own advantages: + +- **Solution 1** consolidates all mapping logic in one place and works well when mappings are tightly related. +- **Solution 2** is simple but can lead to code duplication if you need the nested mapper elsewhere. +- **Solution 3** maintains separation of concerns and reusability but requires manual mapping in the `AfterMap` method. + +Choose the approach that best aligns with your application's architecture and maintainability requirements. + +### More Mapperly Features + +Most of Mapperly's features such as `Ignore` can be configured through its attributes. See the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details. + ## Advanced Topics ### IObjectMapper Interface -Assume that you have created a **reusable module** which defines AutoMapper profiles and uses `IObjectMapper` when it needs to map objects. Your module then can be used in different applications, by nature of the [modularity](../architecture/modularity/basics.md). +Assume that you have created a **reusable module** which defines AutoMapper/Mapperly profiles and uses `IObjectMapper` when it needs to map objects. Your module then can be used in different applications, by nature of the [modularity](../architecture/modularity/basics.md). -`IObjectMapper` is an abstraction and can be replaced by the final application to use another mapping library. The problem here that your reusable module is designed to use the AutoMapper library, because it only defines mappings for it. In such a case, you will want to guarantee that your module always uses AutoMapper even if the final application uses another default object mapping library. +`IObjectMapper` is an abstraction and can be replaced by the final application to use another mapping library. The problem here that your reusable module is designed to use the AutoMapper/Mapperly library, because it only defines mappings for it. In such a case, you will want to guarantee that your module always uses AutoMapper/Mapperly even if the final application uses another default object mapping library. `IObjectMapper` is used to contextualize the object mapper, so you can use different libraries for different modules/contexts. @@ -281,6 +547,8 @@ public class UserAppService : ApplicationService While using the contextualized object mapper is same as the normal object mapper, you should register the contextualized mapper in your module's `ConfigureServices` method: +When using AutoMapper: + ````csharp [DependsOn(typeof(AbpAutoMapperModule))] public class MyModule : AbpModule @@ -298,6 +566,20 @@ public class MyModule : AbpModule } ```` +When using Mapperly: + +````csharp +[DependsOn(typeof(AbpMapperlyModule))] +public class MyModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + //Use Mapperly for MyModule + context.Services.AddMapperlyObjectMapper(); + } +} +```` + `IObjectMapper` is an essential feature for a reusable module where it can be used in multiple applications each may use a different library for object to object mapping. All pre-built ABP modules are using it. But, for the final application, you can ignore this interface and always use the default `IObjectMapper` interface. ### IObjectMapper Interface diff --git a/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md b/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md new file mode 100644 index 0000000000..11112e2bc6 --- /dev/null +++ b/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md @@ -0,0 +1,211 @@ +# Migrating from AutoMapper to Mapperly + +## Introduction + +The AutoMapper library is **no longer free for commercial use**. For more details, you can refer to [this announcement post](https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/). + +ABP Framework provides both AutoMapper and Mapperly integrations. If your project currently uses AutoMapper and you don't have a commercial license, you can switch to Mapperly by following the steps outlined below. + +## Migration Steps + +Please open your project in an IDE(`Visual Studio`, `VS Code` or `JetBrains Rider`), then perform the following global search and replace operations: + +1. Replace `Volo.Abp.AutoMapper` with `Volo.Abp.Mapperly` in all `*.csproj` files. +2. Replace `using Volo.Abp.AutoMapper;` with `using Volo.Abp.Mapperly;` in all `*.cs` files. +3. Replace `AbpAutoMapperModule` with `AbpMapperlyModule` in all `*.cs` files. +4. Replace `AddAutoMapperObjectMapper` with `AddMapperlyObjectMapper` in all `*.cs` files. +5. Remove any code sections that configure `Configure`. +6. Review any existing AutoMapper `Profile` classes and ensure all newly created Mapperly mapping classes are registered appropriately. (You can refer to the example below for guidance) + +**Example:** + +Here is an AutoMapper's `Profile` class: + +```csharp +public class ExampleAutoMapper : Profile +{ + public AbpIdentityApplicationModuleAutoMapperProfile() + { + CreateMap() + .MapExtraProperties() + .Ignore(x => x.IsLockedOut) + .Ignore(x => x.SupportTwoFactor) + .Ignore(x => x.RoleNames); + + CreateMap(); + + CreateMap() + .MapExtraProperties(); + + CreateMap() + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.RoleId, src => src.MapFrom(r => r.Id)); + + CreateMap() + .ForMember(dest => dest.Active, src => src.MapFrom(r => r.IsActive ? "Yes" : "No")) + .ForMember(dest => dest.EmailConfirmed, src => src.MapFrom(r => r.EmailConfirmed ? "Yes" : "No")) + .ForMember(dest => dest.TwoFactorEnabled, src => src.MapFrom(r => r.TwoFactorEnabled ? "Yes" : "No")) + .ForMember(dest => dest.AccountLookout, src => src.MapFrom(r => r.LockoutEnd != null && r.LockoutEnd > DateTime.UtcNow ? "Yes" : "No")) + .Ignore(x => x.Roles); + } +} +``` + +And the Mapperly mapping class: + +```csharp +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToIdentityUserDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))] + [MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))] + [MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))] + public override partial IdentityUserDto Map(IdentityUser source); + + [MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))] + [MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))] + [MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))] + public override partial void Map(IdentityUser source, IdentityUserDto destination); +} + +[Mapper] +public partial class IdentityUserClaimToIdentityUserClaimDtoMapper : MapperBase +{ + public override partial IdentityUserClaimDto Map(IdentityUserClaim source); + + public override partial void Map(IdentityUserClaim source, IdentityUserClaimDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class OrganizationUnitToOrganizationUnitDtoMapper : MapperBase +{ + public override partial OrganizationUnitDto Map(OrganizationUnit source); + public override partial void Map(OrganizationUnit source, OrganizationUnitDto destination); +} + +[Mapper] +public partial class OrganizationUnitRoleToOrganizationUnitRoleDtoMapper : TwoWayMapperBase +{ + public override partial OrganizationUnitRoleDto Map(OrganizationUnitRole source); + public override partial void Map(OrganizationUnitRole source, OrganizationUnitRoleDto destination); + + public override partial OrganizationUnitRole ReverseMap(OrganizationUnitRoleDto destination); + public override partial void ReverseMap(OrganizationUnitRoleDto destination, OrganizationUnitRole source); +} + +[Mapper] +[MapExtraProperties] +public partial class OrganizationUnitToOrganizationUnitWithDetailsDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))] + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))] + public override partial OrganizationUnitWithDetailsDto Map(OrganizationUnit source); + + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))] + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))] + public override partial void Map(OrganizationUnit source, OrganizationUnitWithDetailsDto destination); +} + +[Mapper] +public partial class IdentityRoleToOrganizationUnitRoleDtoMapper : MapperBase +{ + [MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))] + public override partial OrganizationUnitRoleDto Map(IdentityRole source); + + [MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))] + public override partial void Map(IdentityRole source, OrganizationUnitRoleDto destination); +} + +[Mapper] +public partial class IdentityUserToIdentityUserExportDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))] + public override partial IdentityUserExportDto Map(IdentityUser source); + + [MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))] + public override partial void Map(IdentityUser source, IdentityUserExportDto destination); + + public override void AfterMap(IdentityUser source, IdentityUserExportDto destination) + { + destination.Active = source.IsActive ? "Yes" : "No"; + destination.EmailConfirmed = source.EmailConfirmed ? "Yes" : "No"; + destination.TwoFactorEnabled = source.TwoFactorEnabled ? "Yes" : "No"; + destination.AccountLookout = source.LockoutEnd != null && source.LockoutEnd > DateTime.UtcNow ? "Yes" : "No"; + } +} +``` + +## Mapperly Mapping Class + +To use Mapperly, you'll need to create a dedicated mapping class for each source and destination types. + +* Use the `[Mapper]` attribute to designate the class as a Mapperly mapper. +* Replace AutoMapper's `Ignore()` method with the `[MapperIgnoreTarget]` attribute. +* Replace the `MapExtraProperties()` method with the `[MapExtraProperties]` attribute. +* Use the `TwoWayMapperBase` class as an alternative to AutoMapper’s `ReverseMap()` functionality. +* Implement the `AfterMap()` method to execute logic after the mapping is completed. + +### Dependency Injection in Mapper Class + +All Mapperly mapping classes automatically registered in the the [dependency injection (DI)](../../framework/fundamentals/dependency-injection.md) container. To use a service within a Mapper class, simply add it to the constructor, Mapperly will inject it automatically. + +**Example:** + +```csharp +public partial class IdentityUserToIdentityUserDtoMapper : MapperBase +{ + public IdentityUserToIdentityUserDtoMapper(MyService myService) + { + _myService = myService; + } + + public override partial IdentityUserDto Map(IdentityUser source); + public override partial void Map(IdentityUser source, IdentityUserDto destination); + + public override void AfterMap(IdentityUser source, IdentityUserDto destination) + { + destination.MyProperty = _myService.GetMyProperty(source.MyProperty); + } +} +``` + +## Mapperly Documentation + +Please refer to the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details. + +**Key points:** + +- [Mapperly Configuration](https://mapperly.riok.app/docs/configuration/mapper/) +- [Mapperly Enums](https://mapperly.riok.app/docs/configuration/enum/) +- [Mapperly Flattening and unflattening](https://mapperly.riok.app/docs/configuration/flattening/) + + +## Set Default Mapping Provider + +When your project contains modules using both AutoMapper and Mapperly, you may need to explicitly set the default `IAutoObjectMappingProvider` to ensure consistent behavior across your application. + +If your application uses `AutoMapper`: + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddAutoMapperObjectMapper(); +} +``` + +If your application uses `Mapperly`: + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddMapperlyObjectMapper(); +} +``` + +### Why Set Default Mapping Provider? + +When your project contains modules using both AutoMapper and Mapperly, both `AbpAutoMapperModule` and `AbpMapperlyModule` will be loaded. Their dependency order may vary based on your project's module structure, and the last loaded module will override the `IAutoObjectMappingProvider` implementation. This could lead to unexpected behavior. Setting an explicit default ensures predictable mapping behavior throughout your application. diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index 75af841312..b685d72960 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -19,7 +19,7 @@ You have created two modules so far: the **Catalog** module to store and manage In this part and next two pars, you will learn to implement three common patterns for integrating these modules: 1. The Order module will make a request to the Catalog module to get product information when needed. -2. The Product module will listen to events from the Ordering module, so it can decrease a product's stock count when an order is placed. +2. The Catalog module will listen to events from the Ordering module, so it can decrease a product's stock count when an order is placed. 3. Finally, you will execute a database query that includes product and order data. Let's begin from the first one: The Integration Services. diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 4167492d16..e304510e48 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -491,6 +491,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Timing.Tests", "test\Volo.Abp.Timing.Tests\Volo.Abp.Timing.Tests.csproj", "{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Mapperly", "src\Volo.Abp.Mapperly\Volo.Abp.Mapperly.csproj", "{AF556046-54CD-48BC-9740-3E926DB8B510}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Mapperly.Tests", "test\Volo.Abp.Mapperly.Tests\Volo.Abp.Mapperly.Tests.csproj", "{C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.MySQL.Pomelo", "src\Volo.Abp.EntityFrameworkCore.MySQL.Pomelo\Volo.Abp.EntityFrameworkCore.MySQL.Pomelo.csproj", "{5B49FE47-A4C5-45BE-A903-8215CF5E2FAF}" EndProject Global @@ -1467,6 +1471,14 @@ Global {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Debug|Any CPU.Build.0 = Debug|Any CPU {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Release|Any CPU.ActiveCfg = Release|Any CPU {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Release|Any CPU.Build.0 = Release|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Release|Any CPU.Build.0 = Release|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Release|Any CPU.Build.0 = Release|Any CPU {5B49FE47-A4C5-45BE-A903-8215CF5E2FAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5B49FE47-A4C5-45BE-A903-8215CF5E2FAF}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B49FE47-A4C5-45BE-A903-8215CF5E2FAF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1718,6 +1730,8 @@ Global {1BBCBA72-CDB6-4882-96EE-D4CD149433A2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915} = {447C8A77-E5F0-4538-8687-7383196D04EA} {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {AF556046-54CD-48BC-9740-3E926DB8B510} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7} = {447C8A77-E5F0-4538-8687-7383196D04EA} {5B49FE47-A4C5-45BE-A903-8215CF5E2FAF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj index 2e22d5194f..ced3191585 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj @@ -27,7 +27,7 @@ - +
diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs index 3a3b16131d..63c2d6d082 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/DynamicClaims/WebRemoteDynamicClaimsPrincipalContributorCache.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; 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.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs index 9d303579da..0dd2c33cb8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj index a5d4f97e59..b1b3b39f6f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj @@ -20,7 +20,7 @@ - + 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.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj index 084c8aad97..cfa9732d2c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj @@ -28,7 +28,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyAuthenticationStateProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyAuthenticationStateProvider.cs index 0018571c20..c11f6ae9a9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyAuthenticationStateProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyAuthenticationStateProvider.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Security.Claims; using System.Text.Json.Serialization; using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; 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.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs index ed4739c93c..aeb94bc533 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Pages.Abp.MultiTenancy.ClientProxies; using Volo.Abp.Caching; @@ -13,6 +15,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Client; public class MvcRemoteTenantStore : ITenantStore, ITransientDependency { + public ILogger Logger { get; set; } + protected AbpTenantClientProxy TenantAppService { get; } protected IHttpContextAccessor HttpContextAccessor { get; } protected IDistributedCache Cache { get; } @@ -24,6 +28,8 @@ public class MvcRemoteTenantStore : ITenantStore, ITransientDependency IDistributedCache cache, IOptions options) { + Logger = NullLogger.Instance; + TenantAppService = tenantAppService; HttpContextAccessor = httpContextAccessor; Cache = cache; @@ -45,6 +51,11 @@ public class MvcRemoteTenantStore : ITenantStore, ITransientDependency { var tenant = await TenantAppService.FindTenantByNameAsync(normalizedName); tenantConfiguration = await Cache.GetAsync(cacheKey); + if (tenant.Success && tenantConfiguration?.Value == null) + { + Logger.LogWarning($"Tenant with name '{normalizedName}' was found, but not present in the distributed cache, " + + "Ensure all applications use the same distributed cache and the same cache key prefix"); + } } if (httpContext != null) @@ -68,8 +79,13 @@ public class MvcRemoteTenantStore : ITenantStore, ITransientDependency var tenantConfiguration = await Cache.GetAsync(cacheKey); if (tenantConfiguration?.Value == null) { - await TenantAppService.FindTenantByIdAsync(id); + var tenant = await TenantAppService.FindTenantByIdAsync(id); tenantConfiguration = await Cache.GetAsync(cacheKey); + if (tenant.Success && tenantConfiguration?.Value == null) + { + Logger.LogWarning($"Tenant with ID '{id}' was found, but not present in the distributed cache, " + + "Ensure all applications use the same distributed cache and the same cache key prefix"); + } } if (httpContext != null) @@ -98,8 +114,13 @@ public class MvcRemoteTenantStore : ITenantStore, ITransientDependency var tenantConfiguration = Cache.Get(cacheKey); if (tenantConfiguration?.Value == null) { - AsyncHelper.RunSync(async () => await TenantAppService.FindTenantByNameAsync(normalizedName)); + var tenant = AsyncHelper.RunSync(async () => await TenantAppService.FindTenantByNameAsync(normalizedName)); tenantConfiguration = Cache.Get(cacheKey); + if (tenant.Success && tenantConfiguration?.Value == null) + { + Logger.LogWarning($"Tenant with name '{normalizedName}' was found, but not present in the distributed cache, " + + "Ensure all applications use the same distributed cache and the same cache key prefix"); + } } if (httpContext != null) @@ -123,8 +144,13 @@ public class MvcRemoteTenantStore : ITenantStore, ITransientDependency var tenantConfiguration = Cache.Get(cacheKey); if (tenantConfiguration?.Value == null) { - AsyncHelper.RunSync(async () => await TenantAppService.FindTenantByIdAsync(id)); + var tenant = AsyncHelper.RunSync(async () => await TenantAppService.FindTenantByIdAsync(id)); tenantConfiguration = Cache.Get(cacheKey); + if (tenant.Success && tenantConfiguration?.Value == null) + { + Logger.LogWarning($"Tenant with ID '{id}' was found, but not present in the distributed cache, " + + "Ensure all applications use the same distributed cache and the same cache key prefix"); + } } if (httpContext != null) 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/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 081342b913..cb63a9e6c8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -1,5 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +18,7 @@ using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; using Volo.Abp.Content; +using Volo.Abp.Json.SystemTextJson.JsonConverters; namespace Volo.Abp.AspNetCore.Mvc; @@ -32,6 +37,17 @@ internal static class AbpMvcOptionsExtensions private static void AddFormatters(MvcOptions options) { options.OutputFormatters.Insert(0, new RemoteStreamContentOutputFormatter()); + var systemTextJsonOutputFormatter = options.OutputFormatters + .Where(f => f is SystemTextJsonOutputFormatter) + .Cast().FirstOrDefault(); + + if (systemTextJsonOutputFormatter != null) + { + options.OutputFormatters.Remove(systemTextJsonOutputFormatter); + var jsonOptions = new JsonSerializerOptions(systemTextJsonOutputFormatter.SerializerOptions); + jsonOptions.Converters.RemoveAll(x => x is ObjectToInferredTypesConverter); + options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(jsonOptions)); + } } private static void AddConventions(MvcOptions options, IServiceCollection services) 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/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs index 74def27c8e..c37e75ef32 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj index 886e1ed700..3300bce024 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj +++ b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj @@ -30,7 +30,7 @@ - + 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.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs b/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs index e834b3d00d..1c9f4b2dd8 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs @@ -126,7 +126,8 @@ public static class AbpRegistrationBuilderExtensions } else { - if (serviceRegistrationActionList.IsClassInterceptorsDisabled) + if (serviceRegistrationActionList.IsClassInterceptorsDisabled || + serviceRegistrationActionList.DisabledClassInterceptorsSelectors.Any(selector => selector.Predicate(serviceType))) { return registrationBuilder; } diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ClientConfig.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ClientConfig.cs index 02b8397917..19db1beef7 100644 --- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ClientConfig.cs +++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ClientConfig.cs @@ -11,5 +11,8 @@ public class ClientConfig public ServiceBusClientOptions Client { get; set; } = new(); - public ServiceBusProcessorOptions Processor { get; set; } = new(); + public ServiceBusProcessorOptions Processor { get; set; } = new () + { + AutoCompleteMessages = false + }; } 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 367b0716a8..3b1b18e8b3 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.Data; using Volo.Abp.Modularity; @@ -29,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() - .StartAsync(); + .StartAsync(cancellationToken); } } @@ -40,9 +44,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/Auth/AuthService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs index 4b8d71214b..de13a43e37 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs @@ -3,7 +3,7 @@ using System.IO; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using IdentityModel; +using Duende.IdentityModel; using Microsoft.Extensions.Logging; using Volo.Abp.Cli.Http; using Volo.Abp.Cli.ProjectBuilding; 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.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientExtensions.cs index ab77468f3e..f6da2e46e4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClientExtensions.cs @@ -5,7 +5,7 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Polly; using Polly.Extensions.Http; using Volo.Abp.Cli.Auth; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs index e0dcc14fb1..64947e637f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs @@ -19,7 +19,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + @@ -44,7 +44,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase;" + $"{Environment.NewLine}" + @@ -53,7 +53,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + @@ -65,7 +65,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + @@ -77,7 +77,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase ClassUsingNamespaceList = new() + private static readonly List ClassUsingNamespaceList = new() { "using System;", "using System.Collections.Generic;", @@ -90,7 +90,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase InterfaceUsingNamespaceList = new() + private static readonly List InterfaceUsingNamespaceList = new() { "using System;", "using System.Collections.Generic;", @@ -100,7 +100,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase DtoUsingNamespaceList = new() + private static readonly List DtoUsingNamespaceList = new() { "using System;", "using System.Collections.Generic;", @@ -116,7 +116,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase usingNamespaceList) { - var returnSign = returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; + var isAsyncEnumerable = returnTypeName.StartsWith("IAsyncEnumerable<"); + var asyncEnumerableTypeName = isAsyncEnumerable + ? returnTypeName.Substring("IAsyncEnumerable<".Length, returnTypeName.Length - "IAsyncEnumerable<".Length - 1) + : null; + + var returnSign = isAsyncEnumerable ? returnTypeName : returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; - methodBuilder.AppendLine($"public virtual async {returnSign} {action.Name}()"); + methodBuilder.AppendLine(isAsyncEnumerable + ? $"public virtual {returnSign} {action.Name}()" + : $"public virtual async {returnSign} {action.Name}()"); foreach (var parameter in action.ParametersOnMethod) { @@ -325,9 +332,11 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase(nameof({action.Name}), {args});"); + methodBuilder.AppendLine(isAsyncEnumerable + ? $" return RequestAsyncEnumerable<{asyncEnumerableTypeName}>(nameof({action.Name}), {args});" + : returnTypeName == "void" + ? $" await RequestAsync(nameof({action.Name}), {args});" + : $" return await RequestAsync<{returnTypeName}>(nameof({action.Name}), {args});"); foreach (var parameter in action.ParametersOnMethod) { @@ -543,7 +552,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase exposeAction) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ClassInterceptorsSelectorList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ClassInterceptorsSelectorList.cs new file mode 100644 index 0000000000..11abc597c6 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ClassInterceptorsSelectorList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.DependencyInjection; + +public class ClassInterceptorsSelectorList : List, IClassInterceptorsSelectorList +{ + +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IClassInterceptorsSelectorList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IClassInterceptorsSelectorList.cs new file mode 100644 index 0000000000..77991d813e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IClassInterceptorsSelectorList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.DependencyInjection; + +public interface IClassInterceptorsSelectorList : IList +{ + +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceRegistrationActionList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceRegistrationActionList.cs index 8d4ce1b521..750a4c523e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceRegistrationActionList.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceRegistrationActionList.cs @@ -6,4 +6,11 @@ namespace Volo.Abp.DependencyInjection; public class ServiceRegistrationActionList : List> { public bool IsClassInterceptorsDisabled { get; set; } + + public IClassInterceptorsSelectorList DisabledClassInterceptorsSelectors { get; } + + public ServiceRegistrationActionList() + { + DisabledClassInterceptorsSelectors = new ClassInterceptorsSelectorList(); + } } diff --git a/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj b/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj index 8be3e36e3a..699b187bfb 100644 --- a/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj +++ b/framework/src/Volo.Abp.Dapr/Volo.Abp.Dapr.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs index d78b65b5af..dcc59f0a69 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/AbpDaprClientFactory.cs @@ -5,7 +5,7 @@ using System.Net.Http.Headers; using System.Text.Json; using System.Threading.Tasks; using Dapr.Client; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client; 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.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/AbpHttpClientIdentityModelMauiBlazorModule.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/AbpHttpClientIdentityModelMauiBlazorModule.cs index f5df83e9d0..72dc3c1d68 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/AbpHttpClientIdentityModelMauiBlazorModule.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/AbpHttpClientIdentityModelMauiBlazorModule.cs @@ -1,4 +1,4 @@ -using IdentityModel; +using Duende.IdentityModel; using Volo.Abp.AspNetCore.Components.MauiBlazor; using Volo.Abp.Modularity; using Volo.Abp.Security.Claims; diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/MauiIBlazorIdentityModelRemoteServiceHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/MauiIBlazorIdentityModelRemoteServiceHttpClientAuthenticator.cs index b1277373fa..8598009aac 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/MauiIBlazorIdentityModelRemoteServiceHttpClientAuthenticator.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.MauiBlazor/Volo/Abp/Http/Client/IdentityModel/MauiBlazor/MauiIBlazorIdentityModelRemoteServiceHttpClientAuthenticator.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Authentication; using Volo.Abp.IdentityModel; diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextAbpAccessTokenProvider.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextAbpAccessTokenProvider.cs index 5044513438..b993b006e3 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextAbpAccessTokenProvider.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextAbpAccessTokenProvider.cs @@ -1,8 +1,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Authentication; +using Volo.Abp.Users; namespace Volo.Abp.Http.Client.IdentityModel.Web; @@ -24,6 +26,11 @@ public class HttpContextAbpAccessTokenProvider : IAbpAccessTokenProvider, ITrans return null; } + if (!httpContext.RequestServices.GetRequiredService().IsAuthenticated) + { + return null; + } + return await httpContext.GetTokenAsync("access_token"); } } diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextIdentityModelRemoteServiceHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextIdentityModelRemoteServiceHttpClientAuthenticator.cs index 364134348b..b79851f1df 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextIdentityModelRemoteServiceHttpClientAuthenticator.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Volo/Abp/Http/Client/IdentityModel/Web/HttpContextIdentityModelRemoteServiceHttpClientAuthenticator.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Authentication; using Volo.Abp.IdentityModel; diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs index 446011311b..4b59f2c661 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs @@ -1,4 +1,4 @@ -using IdentityModel; +using Duende.IdentityModel; using Volo.Abp.AspNetCore.Components.WebAssembly; using Volo.Abp.Modularity; using Volo.Abp.Security.Claims; diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs index c16317e376..11fa61e05a 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Authentication; using Volo.Abp.IdentityModel; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index d09f97b24c..390adfac9c 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -17,6 +18,7 @@ using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; using Volo.Abp.Threading; @@ -44,6 +46,7 @@ public class ClientProxyBase : ITransientDependency protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService(); protected ICurrentApiVersionInfo CurrentApiVersionInfo => LazyServiceProvider.LazyGetRequiredService(); protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService(); + protected IOptions? SystemTextJsonSerializerOptions => LazyServiceProvider.LazyGetService>(); protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue? arguments = null) { @@ -55,6 +58,21 @@ public class ClientProxyBase : ITransientDependency return await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments)); } + protected virtual async IAsyncEnumerable RequestAsyncEnumerable(string methodName, ClientProxyRequestTypeValue? arguments = null) + { + var requestContext = BuildHttpProxyClientProxyContext(methodName, arguments); + var responseContent = await RequestAsync(requestContext); + var options = SystemTextJsonSerializerOptions?.Value.JsonSerializerOptions; + var stream = await responseContent.ReadAsStreamAsync(); + var items = options != null + ? System.Text.Json.JsonSerializer.DeserializeAsyncEnumerable(stream, options) + : System.Text.Json.JsonSerializer.DeserializeAsyncEnumerable(stream); + await foreach (var item in items) + { + yield return item!; + } + } + protected virtual ClientProxyRequestContext BuildHttpProxyClientProxyContext(string methodName, ClientProxyRequestTypeValue? arguments = null) { if (arguments == null) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs index 09ec183929..69248680cc 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs @@ -224,7 +224,7 @@ public class ClientProxyUrlBuilder : ITransientDependency return true; } - protected virtual Task ConvertValueToStringAsync(object value) + protected virtual Task ConvertValueToStringAsync(object? value) { if (value is DateTime dateTimeValue) { @@ -236,6 +236,6 @@ public class ClientProxyUrlBuilder : ITransientDependency return Task.FromResult(dateTimeValue.ToString("yyyy-MM-ddTHH:mm:ss.fffffff").TrimEnd('0').TrimEnd('.')); } - return Task.FromResult(value.ToString()!); + return Task.FromResult(value?.ToString() ?? string.Empty); } } diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index 8915614bfe..7f318d7215 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs index 011e40b4b3..63da858d06 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using IdentityModel; +using Duende.IdentityModel; namespace Volo.Abp.IdentityModel; diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index 2f0fabdd80..396f2e8989 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -1,5 +1,5 @@ -using IdentityModel; -using IdentityModel.Client; +using Duende.IdentityModel; +using Duende.IdentityModel.Client; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; diff --git a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs index fb60e56a59..1d0e649d28 100644 --- a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs +++ b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs @@ -12,9 +12,9 @@ namespace Volo.Abp.Imaging; public class ImageCompressor : IImageCompressor, ITransientDependency { protected IEnumerable ImageCompressorContributors { get; } - + protected ICancellationTokenProvider CancellationTokenProvider { get; } - + public ImageCompressor(IEnumerable imageCompressorContributors, ICancellationTokenProvider cancellationTokenProvider) { ImageCompressorContributors = imageCompressorContributors.Reverse(); @@ -27,12 +27,12 @@ public class ImageCompressor : IImageCompressor, ITransientDependency CancellationToken cancellationToken = default) { Check.NotNull(stream, nameof(stream)); - + if(!stream.CanRead) { return new ImageCompressResult(stream, ImageProcessState.Unsupported); } - + if(!stream.CanSeek) { var memoryStream = new MemoryStream(); @@ -41,12 +41,12 @@ public class ImageCompressor : IImageCompressor, ITransientDependency stream = memoryStream; } - foreach (var imageCompressorContributor in ImageCompressorContributors) + foreach (var imageCompressorContributor in ImageCompressorContributors.Reverse()) { var result = await imageCompressorContributor.TryCompressAsync(stream, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); - + SeekToBegin(stream); - + if (result.State == ImageProcessState.Unsupported) { continue; @@ -54,7 +54,7 @@ public class ImageCompressor : IImageCompressor, ITransientDependency return result; } - + return new ImageCompressResult(stream, ImageProcessState.Unsupported); } @@ -64,22 +64,22 @@ public class ImageCompressor : IImageCompressor, ITransientDependency CancellationToken cancellationToken = default) { Check.NotNull(bytes, nameof(bytes)); - - foreach (var imageCompressorContributor in ImageCompressorContributors) + + foreach (var imageCompressorContributor in ImageCompressorContributors.Reverse()) { var result = await imageCompressorContributor.TryCompressAsync(bytes, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); - + if (result.State == ImageProcessState.Unsupported) { continue; } - + return result; } - + return new ImageCompressResult(bytes, ImageProcessState.Unsupported); } - + protected virtual void SeekToBegin(Stream stream) { if (stream.CanSeek) @@ -87,4 +87,4 @@ public class ImageCompressor : IImageCompressor, ITransientDependency stream.Seek(0, SeekOrigin.Begin); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs index 80d9ec8815..96bf69b2aa 100644 --- a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs +++ b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs @@ -13,36 +13,36 @@ namespace Volo.Abp.Imaging; public class ImageResizer : IImageResizer, ITransientDependency { protected IEnumerable ImageResizerContributors { get; } - + protected ImageResizeOptions ImageResizeOptions { get; } - + protected ICancellationTokenProvider CancellationTokenProvider { get; } - + public ImageResizer( - IEnumerable imageResizerContributors, - IOptions imageResizeOptions, + IEnumerable imageResizerContributors, + IOptions imageResizeOptions, ICancellationTokenProvider cancellationTokenProvider) { ImageResizerContributors = imageResizerContributors.Reverse(); CancellationTokenProvider = cancellationTokenProvider; ImageResizeOptions = imageResizeOptions.Value; } - + public virtual async Task> ResizeAsync( - [NotNull] Stream stream, - ImageResizeArgs resizeArgs, - string? mimeType = null, + [NotNull] Stream stream, + ImageResizeArgs resizeArgs, + string? mimeType = null, CancellationToken cancellationToken = default) { Check.NotNull(stream, nameof(stream)); - + ChangeDefaultResizeMode(resizeArgs); - + if(!stream.CanRead) { return new ImageResizeResult(stream, ImageProcessState.Unsupported); } - + if(!stream.CanSeek) { var memoryStream = new MemoryStream(); @@ -50,13 +50,13 @@ public class ImageResizer : IImageResizer, ITransientDependency SeekToBegin(memoryStream); stream = memoryStream; } - - foreach (var imageResizerContributor in ImageResizerContributors) + + foreach (var imageResizerContributor in ImageResizerContributors.Reverse()) { var result = await imageResizerContributor.TryResizeAsync(stream, resizeArgs, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); SeekToBegin(stream); - + if (result.State == ImageProcessState.Unsupported) { continue; @@ -64,24 +64,24 @@ public class ImageResizer : IImageResizer, ITransientDependency return result; } - + return new ImageResizeResult(stream, ImageProcessState.Unsupported); } public virtual async Task> ResizeAsync( - [NotNull] byte[] bytes, - ImageResizeArgs resizeArgs, - string? mimeType = null, + [NotNull] byte[] bytes, + ImageResizeArgs resizeArgs, + string? mimeType = null, CancellationToken cancellationToken = default) { Check.NotNull(bytes, nameof(bytes)); - + ChangeDefaultResizeMode(resizeArgs); - - foreach (var imageResizerContributor in ImageResizerContributors) + + foreach (var imageResizerContributor in ImageResizerContributors.Reverse()) { var result = await imageResizerContributor.TryResizeAsync(bytes, resizeArgs, mimeType, CancellationTokenProvider.FallbackToProvider(cancellationToken)); - + if (result.State == ImageProcessState.Unsupported) { continue; @@ -89,10 +89,10 @@ public class ImageResizer : IImageResizer, ITransientDependency return result; } - + return new ImageResizeResult(bytes, ImageProcessState.Unsupported); } - + protected virtual void ChangeDefaultResizeMode(ImageResizeArgs resizeArgs) { if (resizeArgs.Mode == ImageResizeMode.Default) @@ -100,7 +100,7 @@ public class ImageResizer : IImageResizer, ITransientDependency resizeArgs.Mode = ImageResizeOptions.DefaultResizeMode; } } - + protected virtual void SeekToBegin(Stream stream) { if (stream.CanSeek) @@ -108,4 +108,4 @@ public class ImageResizer : IImageResizer, ITransientDependency stream.Seek(0, SeekOrigin.Begin); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs new file mode 100644 index 0000000000..516e7b969f --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Mapperly; +using Volo.Abp.ObjectMapping; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class AbpAutoMapperServiceCollectionExtensions +{ + public static IServiceCollection AddMapperlyObjectMapper(this IServiceCollection services) + { + return services.Replace( + ServiceDescriptor.Transient() + ); + } + + public static IServiceCollection AddMapperlyObjectMapper(this IServiceCollection services) + { + return services.Replace( + ServiceDescriptor.Transient, MapperlyAutoObjectMappingProvider>() + ); + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj b/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj new file mode 100644 index 0000000000..7c085a146e --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj @@ -0,0 +1,29 @@ + + + + + + + net9.0 + enable + Nullable + Volo.Abp.Mapperly + Volo.Abp.Mapperly + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs new file mode 100644 index 0000000000..028a72bb67 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Mapperly; + +public class AbpMapperlyConventionalRegistrar : DefaultConventionalRegistrar +{ + protected override bool IsConventionalRegistrationDisabled(Type type) + { + return !type.GetInterfaces().Any(x => x.IsGenericType && typeof(IAbpMapperlyMapper<,>) == x.GetGenericTypeDefinition()) || + base.IsConventionalRegistrationDisabled(type); + } + + protected override List GetExposedServiceTypes(Type type) + { + var exposedServiceTypes = base.GetExposedServiceTypes(type); + var mapperlyInterfaces = type.GetInterfaces().Where(x => + x.IsGenericType && (typeof(IAbpMapperlyMapper<,>) == x.GetGenericTypeDefinition() || + typeof(IAbpReverseMapperlyMapper<,>) == x.GetGenericTypeDefinition())); + return exposedServiceTypes + .Union(mapperlyInterfaces) + .Distinct() + .ToList(); + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs new file mode 100644 index 0000000000..f879354ed3 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Auditing; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly; + +[DependsOn( + typeof(AbpObjectMappingModule), + typeof(AbpObjectExtendingModule), + typeof(AbpAuditingModule) +)] +public class AbpMapperlyModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddConventionalRegistrar(new AbpMapperlyConventionalRegistrar()); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + // This is the temporary solution, We will remove it in when all apps are migrated to Mapperly. + var disableMapperlyAutoObjectMappingProvider = false; + + var modules = context.Services.GetSingletonInstance().Modules.ToList(); + var autoMapperModuleIndex = modules.FindIndex(x => x.Type.FullName!.Equals("Volo.Abp.AutoMapper.AbpAutoMapperModule", StringComparison.OrdinalIgnoreCase)); + if (autoMapperModuleIndex >= 0) + { + var mapperlyModuleIndex = modules.FindIndex(x => x.Type == typeof(AbpMapperlyModule)); + if (mapperlyModuleIndex > autoMapperModuleIndex) + { + disableMapperlyAutoObjectMappingProvider = true; + } + } + + if (!disableMapperlyAutoObjectMappingProvider) + { + context.Services.AddMapperlyObjectMapper(); + } + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs new file mode 100644 index 0000000000..eac0f6644c --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs @@ -0,0 +1,23 @@ +namespace Volo.Abp.Mapperly; + +public interface IAbpMapperlyMapper +{ + TDestination Map(TSource source); + + void Map(TSource source, TDestination destination); + + void BeforeMap(TSource source); + + void AfterMap(TSource source, TDestination destination); +} + +public interface IAbpReverseMapperlyMapper : IAbpMapperlyMapper +{ + TSource ReverseMap(TDestination destination); + + void ReverseMap(TDestination destination, TSource source); + + void BeforeReverseMap(TDestination destination); + + void AfterReverseMap(TDestination destination, TSource source); +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs new file mode 100644 index 0000000000..705a1af72d --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Mapperly; + +[AttributeUsage(AttributeTargets.Class)] +public class MapExtraPropertiesAttribute : Attribute +{ + public MappingPropertyDefinitionChecks DefinitionChecks { get; set; } = MappingPropertyDefinitionChecks.Null; + + public string[]? IgnoredProperties { get; set; } + + public bool MapToRegularProperties { get; set; } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperBase.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperBase.cs new file mode 100644 index 0000000000..39d9dce995 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperBase.cs @@ -0,0 +1,32 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Mapperly; + +public abstract class MapperBase : IAbpMapperlyMapper, ITransientDependency +{ + public abstract TDestination Map(TSource source); + + public abstract void Map(TSource source, TDestination destination); + + public virtual void BeforeMap(TSource source) + { + } + public virtual void AfterMap(TSource source, TDestination destination) + { + } +} + +public abstract class TwoWayMapperBase : MapperBase, IAbpReverseMapperlyMapper +{ + public abstract TSource ReverseMap(TDestination destination); + + public abstract void ReverseMap(TDestination destination, TSource source); + + public virtual void BeforeReverseMap(TDestination destination) + { + } + + public virtual void AfterReverseMap(TDestination destination, TSource source) + { + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs new file mode 100644 index 0000000000..677649b79a --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Mapperly; + +public class MapperlyAutoObjectMappingProvider : MapperlyAutoObjectMappingProvider, IAutoObjectMappingProvider +{ + public MapperlyAutoObjectMappingProvider(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } +} + +public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider +{ + protected static readonly ConcurrentDictionary> MapCache = new(); + + protected IServiceProvider ServiceProvider { get; } + + public MapperlyAutoObjectMappingProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public virtual TDestination Map(object source) + { + if (TryToMapCollection((TSource)source, default, out var collectionResult)) + { + return collectionResult; + } + + var mapper = ServiceProvider.GetService>(); + if (mapper != null) + { + mapper.BeforeMap((TSource)source); + var destination = mapper.Map((TSource)source); + TryMapExtraProperties(mapper.GetType().GetSingleAttributeOrNull(), (TSource)source, destination, new ExtraPropertyDictionary()); + mapper.AfterMap((TSource)source, destination); + return destination; + } + + var reverseMapper = ServiceProvider.GetService>(); + if (reverseMapper != null) + { + reverseMapper.BeforeReverseMap((TSource)source); + var destination = reverseMapper.ReverseMap((TSource)source); + TryMapExtraProperties(reverseMapper.GetType().GetSingleAttributeOrNull(), (TSource)source, destination, GetExtraProperties(destination)); + reverseMapper.AfterReverseMap((TSource)source, destination); + return destination; + } + + throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperlyMapper))} or" + + $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperlyMapper))} was found"); + } + + public virtual TDestination Map(TSource source, TDestination destination) + { + if (TryToMapCollection(source, destination, out var collectionResult)) + { + return collectionResult; + } + + var mapper = ServiceProvider.GetService>(); + if (mapper != null) + { + mapper.BeforeMap(source); + var destinationExtraProperties = GetExtraProperties(destination); + mapper.Map(source, destination); + TryMapExtraProperties(mapper.GetType().GetSingleAttributeOrNull(), source, destination, destinationExtraProperties); + mapper.AfterMap(source, destination); + return destination; + } + + var reverseMapper = ServiceProvider.GetService>(); + if (reverseMapper != null) + { + reverseMapper.BeforeReverseMap(source); + var destinationExtraProperties = GetExtraProperties(destination); + reverseMapper.ReverseMap(source, destination); + TryMapExtraProperties(reverseMapper.GetType().GetSingleAttributeOrNull(), source, destination, destinationExtraProperties); + reverseMapper.AfterReverseMap(source, destination); + return destination; + } + + throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperlyMapper))} or" + + $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperlyMapper))} was found"); + } + + protected virtual bool TryToMapCollection(TSource source, TDestination? destination, out TDestination collectionResult) + { + if (!ObjectMappingHelper.IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) + { + collectionResult = default!; + return false; + } + + var mapperType = typeof(IAbpMapperlyMapper<,>).MakeGenericType(sourceArgumentType, destinationArgumentType); + var mapper = ServiceProvider.GetService(mapperType); + if (mapper == null) + { + mapperType = typeof(IAbpReverseMapperlyMapper<,>).MakeGenericType(destinationArgumentType, sourceArgumentType); + mapper = ServiceProvider.GetService(mapperType); + if (mapper == null) + { + //skip, no specific mapper + collectionResult = default!; + return false; + } + } + + var invoker = MapCache.GetOrAdd( + $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}", + _ => CreateMapDelegate(mapperType, sourceArgumentType, destinationArgumentType, destination != null)); + + var sourceList = source!.As(); + var result = definitionGenericType.IsGenericType + ? Activator.CreateInstance(definitionGenericType.MakeGenericType(destinationArgumentType))!.As() + : Array.CreateInstance(destinationArgumentType, sourceList.Count); + + if (destination != null && !destination.GetType().IsArray) + { + //Clear destination collection if destination not an array, We won't change array just same behavior as AutoMapper. + destination.As().Clear(); + } + + for (var i = 0; i < sourceList.Count; i++) + { + var invokeResult = destination == null + ? invoker(this, sourceList[i]!, null!) + : invoker(this, sourceList[i]!, Activator.CreateInstance(destinationArgumentType)!); + + if (definitionGenericType.IsGenericType) + { + result.Add(invokeResult); + destination?.As().Add(invokeResult); + } + else + { + result[i] = invokeResult; + } + } + + if (destination != null && destination.GetType().IsArray) + { + //Return the new collection if destination is an array, We won't change array just same behavior as AutoMapper. + collectionResult = (TDestination)result; + return true; + } + + //Return the destination if destination exists. The parameter reference equals with return object. + collectionResult = destination ?? (TDestination)result; + return true; + } + + protected virtual Func CreateMapDelegate( + Type mapperType, + Type sourceArgumentType, + Type destinationArgumentType, + bool hasDestination) + { + var methods = typeof(MapperlyAutoObjectMappingProvider) + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name == nameof(Map)) + .Where(x => + { + var parameters = x.GetParameters(); + return (hasDestination || parameters.Length == 1) && + (!hasDestination || parameters.Length == 2); + }) + .ToList(); + + if (methods.Count == 0) + { + throw new AbpException($"Could not find a method named '{nameof(Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); + } + + if (methods.Count > 1) + { + throw new AbpException($"Found more than one method named '{nameof(Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); + } + + var method = methods[0].MakeGenericMethod(sourceArgumentType, destinationArgumentType); + + var instanceParam = Expression.Parameter(typeof(object), "mapper"); + var sourceParam = Expression.Parameter(typeof(object), "source"); + var destinationParam = Expression.Parameter(typeof(object), "destination"); + + var instanceCast = Expression.Convert(instanceParam, method.DeclaringType!); + var callParams = new List + { + Expression.Convert(sourceParam, sourceArgumentType) + }; + + if (hasDestination) + { + callParams.Add(Expression.Convert(destinationParam, destinationArgumentType)); + } + + var call = Expression.Call(instanceCast, method, callParams); + var callConvert = Expression.Convert(call, typeof(object)); + + return Expression.Lambda>(callConvert, instanceParam, sourceParam, destinationParam).Compile(); + } + + protected virtual ExtraPropertyDictionary GetExtraProperties(TDestination destination) + { + var extraProperties = new ExtraPropertyDictionary(); + if (destination is not IHasExtraProperties hasExtraProperties) + { + return extraProperties; + } + + foreach (var property in hasExtraProperties.ExtraProperties) + { + extraProperties.Add(property.Key, property.Value); + } + return extraProperties; + } + + protected virtual void TryMapExtraProperties(MapExtraPropertiesAttribute? mapExtraPropertiesAttribute, TSource source, TDestination destination, ExtraPropertyDictionary destinationExtraProperty) + { + if (mapExtraPropertiesAttribute != null && + typeof(IHasExtraProperties).IsAssignableFrom(typeof(TDestination)) && + typeof(IHasExtraProperties).IsAssignableFrom(typeof(TSource))) + { + MapExtraProperties( + source!.As(), + destination!.As(), + destinationExtraProperty, + mapExtraPropertiesAttribute.DefinitionChecks, + mapExtraPropertiesAttribute.IgnoredProperties, + mapExtraPropertiesAttribute.MapToRegularProperties + ); + } + } + protected virtual void MapExtraProperties( + IHasExtraProperties source, + IHasExtraProperties destination, + ExtraPropertyDictionary destinationExtraProperty, + MappingPropertyDefinitionChecks? definitionChecks = null, + string[]? ignoredProperties = null, + bool mapToRegularProperties = false) + { + var result = destinationExtraProperty.IsNullOrEmpty() + ? new Dictionary() + : new Dictionary(destinationExtraProperty); + + if (source.ExtraProperties != null && destination.ExtraProperties != null) + { + ExtensibleObjectMapper + .MapExtraPropertiesTo( + typeof(TSource), + typeof(TDestination), + source.ExtraProperties, + result, + definitionChecks, + ignoredProperties + ); + } + + ObjectHelper.TrySetProperty(destination, x => x.ExtraProperties, () => new ExtraPropertyDictionary(result)); + if (mapToRegularProperties) + { + destination.SetExtraPropertiesToRegularProperties(); + } + } +} 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/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs index eae85f77d7..48a3d37916 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs @@ -188,7 +188,7 @@ public static class ExtensibleObjectMapper return false; } - if (definitionChecks != null) + if (definitionChecks != null && definitionChecks.Value != MappingPropertyDefinitionChecks.Null) { if (definitionChecks.Value.HasFlag(MappingPropertyDefinitionChecks.Source)) { diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs index 04668476a0..f717e97714 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs @@ -5,20 +5,25 @@ namespace Volo.Abp.ObjectExtending; [Flags] public enum MappingPropertyDefinitionChecks : byte { + /// + /// Same as Null, We need to use this in Attribute to avoid null checks. + /// + Null = 0, + /// /// No check. Copy all extra properties from the source to the destination. /// - None = 0, + None = 1 << 0, /// /// Copy the extra properties defined for the source class. /// - Source = 1, + Source = 1 << 1, /// /// Copy the extra properties defined for the destination class. /// - Destination = 2, + Destination = 1 << 2, /// /// Copy extra properties defined for both of the source and destination classes. diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs index 30733b1e24..faf8c4e258 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using Volo.Abp.DependencyInjection; @@ -25,7 +26,7 @@ public class DefaultObjectMapper : DefaultObjectMapper, IObjectMapper< public class DefaultObjectMapper : IObjectMapper, ITransientDependency { - protected static ConcurrentDictionary MethodInfoCache { get; } = new ConcurrentDictionary(); + protected static readonly ConcurrentDictionary> MapCache = new(); public IAutoObjectMappingProvider AutoObjectMappingProvider { get; } protected IServiceProvider ServiceProvider { get; } @@ -122,7 +123,7 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency protected virtual bool TryToMapCollection(IServiceScope serviceScope, TSource source, TDestination? destination, out TDestination collectionResult) { - if (!IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) + if (!ObjectMappingHelper.IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) { collectionResult = default!; return false; @@ -137,46 +138,9 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return false; } - var cacheKey = $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}"; - var method = MethodInfoCache.GetOrAdd( - cacheKey, - _ => - { - var methods = specificMapper - .GetType() - .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(x => x.Name == nameof(IObjectMapper.Map)) - .Where(x => - { - var parameters = x.GetParameters(); - if (destination == null && parameters.Length != 1 || - destination != null && parameters.Length != 2 || - parameters[0].ParameterType != sourceArgumentType) - { - return false; - } - - return destination == null || parameters[1].ParameterType == destinationArgumentType; - }) - .ToList(); - - if (methods.IsNullOrEmpty()) - { - throw new AbpException($"Could not find a method named '{nameof(IObjectMapper.Map)}'" + - $" with parameters({(destination == null ? sourceArgumentType.ToString() : sourceArgumentType + "," + destinationArgumentType)})" + - $" in the type '{mapperType}'."); - } - - if (methods.Count > 1) - { - throw new AbpException($"Found more than one method named '{nameof(IObjectMapper.Map)}'" + - $" with parameters({(destination == null ? sourceArgumentType.ToString() : sourceArgumentType + "," + destinationArgumentType)})" + - $" in the type '{mapperType}'."); - } - - return methods.First(); - } - ); + var invoker = MapCache.GetOrAdd( + $"{mapperType.FullName}_{(destination == null ? "MapMethodWithSingleParameter" : "MapMethodWithDoubleParameters")}", + _ => CreateMapDelegate(mapperType, sourceArgumentType, destinationArgumentType, destination != null)); var sourceList = source!.As(); var result = definitionGenericType.IsGenericType @@ -192,8 +156,8 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency for (var i = 0; i < sourceList.Count; i++) { var invokeResult = destination == null - ? method.Invoke(specificMapper, new [] { sourceList[i] })! - : method.Invoke(specificMapper, new [] { sourceList[i], Activator.CreateInstance(destinationArgumentType)! })!; + ? invoker(specificMapper, sourceList[i]!, null!) + : invoker(specificMapper, sourceList[i]!, Activator.CreateInstance(destinationArgumentType)!); if (definitionGenericType.IsGenericType) { @@ -218,61 +182,64 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency return true; } - protected virtual bool IsCollectionGenericType(out Type sourceArgumentType, out Type destinationArgumentType, out Type definitionGenericType) + protected virtual Func CreateMapDelegate( + Type mapperType, + Type sourceArgumentType, + Type destinationArgumentType, + bool hasDestination) { - sourceArgumentType = default!; - destinationArgumentType = default!; - definitionGenericType = default!; - - if ((!typeof(TSource).IsGenericType && !typeof(TSource).IsArray) || - (!typeof(TDestination).IsGenericType && !typeof(TDestination).IsArray)) - { - return false; - } + var methods = mapperType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name == nameof(IObjectMapper.Map)) + .Where(x => + { + var parameters = x.GetParameters(); + if (!hasDestination && parameters.Length != 1 || + hasDestination && parameters.Length != 2 || + parameters[0].ParameterType != sourceArgumentType) + { + return false; + } - var supportedCollectionTypes = new[] - { - typeof(IEnumerable<>), - typeof(ICollection<>), - typeof(Collection<>), - typeof(IList<>), - typeof(List<>) - }; + return !hasDestination || parameters[1].ParameterType == destinationArgumentType; + }) + .ToList(); - if (typeof(TSource).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TSource).GetGenericTypeDefinition())) + if (methods.Count == 0) { - sourceArgumentType = typeof(TSource).GenericTypeArguments[0]; + throw new AbpException($"Could not find a method named '{nameof(IObjectMapper.Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); } - if (typeof(TSource).IsArray) + if (methods.Count > 1) { - sourceArgumentType = typeof(TSource).GetElementType()!; + throw new AbpException($"Found more than one method named '{nameof(IObjectMapper.Map)}'" + + $" with parameters({(hasDestination ? sourceArgumentType + ", " + destinationArgumentType : sourceArgumentType.ToString())})" + + $" in the type '{mapperType}'."); } - if (sourceArgumentType == default!) - { - return false; - } + var method = methods[0]; - definitionGenericType = typeof(List<>); - if (typeof(TDestination).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TDestination).GetGenericTypeDefinition())) - { - destinationArgumentType = typeof(TDestination).GenericTypeArguments[0]; + var instanceParam = Expression.Parameter(typeof(object), "mapper"); + var sourceParam = Expression.Parameter(typeof(object), "source"); + var destinationParam = Expression.Parameter(typeof(object), "destination"); - if (typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) || - typeof(TDestination).GetGenericTypeDefinition() == typeof(Collection<>)) - { - definitionGenericType = typeof(Collection<>); - } - } + var instanceCast = Expression.Convert(instanceParam, method.DeclaringType!); + var callParams = new List + { + Expression.Convert(sourceParam, sourceArgumentType) + }; - if (typeof(TDestination).IsArray) + if (hasDestination) { - destinationArgumentType = typeof(TDestination).GetElementType()!; - definitionGenericType = typeof(Array); + callParams.Add(Expression.Convert(destinationParam, destinationArgumentType)); } - return destinationArgumentType != default!; + var call = Expression.Call(instanceCast, method, callParams); + var callConvert = Expression.Convert(call, typeof(object)); + + return Expression.Lambda>(callConvert, instanceParam, sourceParam, destinationParam).Compile(); } protected virtual TDestination AutoMap(object source) diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs new file mode 100644 index 0000000000..5949edaba9 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/ObjectMappingHelper.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Volo.Abp.ObjectMapping; + +public static class ObjectMappingHelper +{ + private static readonly ConcurrentDictionary<(Type, Type), (Type sourceArgumentType, Type destinationArgumentType, Type definitionGenericType)?> Cache = new(); + + public static bool IsCollectionGenericType( + out Type sourceArgumentType, + out Type destinationArgumentType, + out Type definitionGenericType) + { + var cached = Cache.GetOrAdd((typeof(TSource), typeof(TDestination)), _ => IsCollectionGenericTypeInternal()); + if (cached == null) + { + sourceArgumentType = destinationArgumentType = definitionGenericType = null!; + return false; + } + + (sourceArgumentType, destinationArgumentType, definitionGenericType) = cached.Value; + return true; + } + + private static (Type, Type, Type)? IsCollectionGenericTypeInternal() + { + if (!IsCollectionGenericTypeInternal(typeof(TSource), out var sourceArgumentType, out _) || + !IsCollectionGenericTypeInternal(typeof(TDestination), out var destinationArgumentType, out var definitionGenericType)) + { + return null; + } + + return (sourceArgumentType, destinationArgumentType, definitionGenericType); + } + + private static bool IsCollectionGenericTypeInternal(Type type, out Type elementType, out Type definitionGenericType) + { + var supportedCollectionTypes = new[] + { + typeof(IEnumerable<>), + typeof(ICollection<>), + typeof(Collection<>), + typeof(IList<>), + typeof(List<>) + }; + + if (type.IsArray) + { + elementType = type.GetElementType()!; + definitionGenericType = type; + return true; + } + + if (type.IsGenericType && + supportedCollectionTypes.Contains(type.GetGenericTypeDefinition()) || + type.GetInterfaces().Any(i => i.IsGenericType && supportedCollectionTypes.Contains(i.GetGenericTypeDefinition()))) + { + elementType = type.GetGenericArguments()[0]; + definitionGenericType = type.GetGenericTypeDefinition(); + if (definitionGenericType == typeof(IEnumerable<>) || + definitionGenericType == typeof(IList<>)) + { + definitionGenericType = typeof(List<>); + } + + if (definitionGenericType == typeof(ICollection<>)) + { + definitionGenericType = typeof(Collection<>); + } + return true; + } + + elementType = null!; + definitionGenericType = null!; + return false; + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs index 331a3c7bdf..71fdbf0c17 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/RemoteDynamicClaimsPrincipalContributorCacheBase.cs @@ -42,7 +42,8 @@ public abstract class RemoteDynamicClaimsPrincipalContributorCacheBase(); + 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.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs new file mode 100644 index 0000000000..9e5fe4f8cf --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Mapperly; +using Volo.Abp.ObjectExtending.TestObjects; +using Volo.Abp.Testing; +using Xunit; + +namespace Mapperly; + +public class AbpAutoMapperExtensibleDtoExtensions_Tests : AbpIntegratedTest +{ + private readonly Volo.Abp.ObjectMapping.IObjectMapper _objectMapper; + + public AbpAutoMapperExtensibleDtoExtensions_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default() + { + var person = new ExtensibleTestPerson() + .SetProperty("Name", "John") + .SetProperty("Age", 42) + .SetProperty("ChildCount", 2) + .SetProperty("Sex", "male") + .SetProperty("CityName", "Adana"); + + var personDto = new ExtensibleTestPersonDto() + .SetProperty("ExistingDtoProperty", "existing-value"); + + _objectMapper.Map(person, personDto); + + personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes + personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + personDto.GetProperty("ChildCount").ShouldBe(0); //Not defined in the source, but was set to the default value by ExtensibleTestPersonDto constructor + personDto.GetProperty("CityName").ShouldBeNull(); //Ignored, but was set to the default value by ExtensibleTestPersonDto constructor + personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination + personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes + } + + [Fact] + public void MapExtraProperties_Also_Should_Map_To_RegularProperties() + { + var person = new ExtensibleTestPerson() + .SetProperty("Name", "John") + .SetProperty("Age", 42); + + var personDto = new ExtensibleTestPersonWithRegularPropertiesDto() + .SetProperty("IsActive", true); + + _objectMapper.Map(person, personDto); + + //Defined in both classes + personDto.HasProperty("Name").ShouldBe(false); + personDto.Name.ShouldBe("John"); + + //Defined in both classes + personDto.HasProperty("Age").ShouldBe(false); + personDto.Age.ShouldBe(42); + + //Should not clear existing values + personDto.HasProperty("IsActive").ShouldBe(false); + personDto.IsActive.ShouldBe(true); + } + + [Fact(Skip = "Mapperly requires IHasExtraProperties.ExtraPropertyDictionary to be marked as nullable")] + public void MapExtraPropertiesTo_Should_Ignored_If_ExtraProperties_Is_Null() + { + var person = new ExtensibleTestPerson(); + person.SetExtraPropertiesAsNull(); + + var personDto = new ExtensibleTestPersonDto(); + personDto.SetExtraPropertiesAsNull(); + + Should.NotThrow(() => _objectMapper.Map(person, personDto)); + + person.ExtraProperties.ShouldBe(null); + personDto.ExtraProperties.ShouldBeEmpty(); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj b/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj new file mode 100644 index 0000000000..2ea799a4c9 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj @@ -0,0 +1,17 @@ + + + + + + net9.0 + Volo.Abp.Mapperly.Tests + Volo.Abp.Mapperly.Tests + + + + + + + + + diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs new file mode 100644 index 0000000000..1c07bff75b --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.DependencyInjection; +using Riok.Mapperly.Abstractions; +using Shouldly; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class MyClass +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +public class MyClassDto +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +[Mapper] +public partial class MyClassMapper : MapperBase +{ + public override partial MyClassDto Map(MyClass source); + + public override partial void Map(MyClass source, MyClassDto destination); + + public override void BeforeMap(MyClass source) + { + source.Name = "BeforeMap " + source.Name; + } + + public override void AfterMap(MyClass source, MyClassDto destination) + { + destination.Name = source.Name + " AfterMap"; + } +} + +public class AbpMapperlyBeforeAndAfterMethod_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpMapperlyBeforeAndAfterMethod_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void BeforeAndAfterMethods_Should_Be_Called_When_Mapping() + { + var myClass = new MyClass { Id = "1", Name = "Test" }; + + var myClassDto = _objectMapper.Map(myClass); + myClassDto.Name.ShouldBe("BeforeMap Test AfterMap"); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs new file mode 100644 index 0000000000..3f0773f0a1 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.JavaScript; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpMapperlyModule_Basic_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Replace_IAutoObjectMappingProvider() + { + Assert.True(ServiceProvider.GetRequiredService() is MapperlyAutoObjectMappingProvider); + } + + [Fact] + public void Should_Map_Objects_With_AutoMap_Attributes() + { + var dto = _objectMapper.Map(new MyEntity { Number = 42 }); + dto.Number.ShouldBe(42); + } + + [Fact] + public void Should_Map_Objects_With_Existing_Target_Object() + { + var dto = new MyEntityDto {Id = Guid.Empty, Number = 42}; + + _objectMapper.Map(new MyEntity { Id = Guid.NewGuid(), Number = 43 }, dto); + + dto.Number.ShouldBe(43); + dto.Id.ShouldNotBe(Guid.Empty); + } + + [Fact] + public void Should_Map_Collection() + { + var dto = _objectMapper.Map, List>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }); + + dto.Count.ShouldBe(2); + dto[0].Number.ShouldBe(42); + dto[1].Number.ShouldBe(43); + + var dto2 = _objectMapper.Map, MyEntityDto[]>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }.AsReadOnly()); + + dto2.Length.ShouldBe(2); + dto2[0].Number.ShouldBe(42); + dto2[1].Number.ShouldBe(43); + + var dtoList = new List(); + { + new MyEntityDto() { Number = 44 }; + new MyEntityDto() { Number = 45 }; + } + + _objectMapper.Map, List>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }, dtoList); + + dtoList.Count.ShouldBe(2); + dtoList[0].Number.ShouldBe(42); + dtoList[1].Number.ShouldBe(43); + + var dtoArray = dtoList.ToArray(); + _objectMapper.Map, MyEntityDto[]>(new List + { + new MyEntity { Number = 42 }, + new MyEntity { Number = 43 } + }.AsReadOnly(), dtoArray); + + dtoArray.Length.ShouldBe(2); + dtoArray[0].Number.ShouldBe(42); + dtoArray[1].Number.ShouldBe(43); + } + + [Fact] + public void Should_Map_Enum() + { + var dto = _objectMapper.Map(MyEnum.Value3); + dto.ShouldBe(MyEnumDto.Value2); //Value2 is same as Value3 + } + + [Fact] + public void Should_Throw_Exception_If_Mapper_Is_Not_Found() + { + var exception = Assert.Throws(() =>_objectMapper.Map(new MyEntity())); + exception.Message.ShouldBe("No " + + "Volo.Abp.Mapperly.IAbpMapperlyMapper or " + + "Volo.Abp.Mapperly.IAbpReverseMapperlyMapper" + + " was found"); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Specific_ObjectMapper_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Specific_ObjectMapper_Tests.cs new file mode 100644 index 0000000000..60f4294aeb --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Specific_ObjectMapper_Tests.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class AbpMapperlyModule_Specific_ObjectMapper_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpMapperlyModule_Specific_ObjectMapper_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Use_Specific_Object_Mapper_If_Registered() + { + var dto = _objectMapper.Map(new MyEntity { Number = 42 }); + dto.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + } + + [Fact] + public void Specific_Object_Mapper_Should_Be_Used_For_Collections_If_Registered() + { + // IEnumerable + _objectMapper.Map, IEnumerable>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var destination = new List() + { + new MyEntityDto2 { Number = 44 } + }; + var returnIEnumerable = _objectMapper.Map, IEnumerable>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnIEnumerable.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnIEnumerable).ShouldBeTrue(); + + // ICollection + _objectMapper.Map, ICollection>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var returnICollection = _objectMapper.Map, ICollection>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnICollection.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnICollection).ShouldBeTrue(); + + // Collection + _objectMapper.Map, Collection>(new Collection() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var destination2 = new Collection() + { + new MyEntityDto2 { Number = 44 } + }; + var returnCollection = _objectMapper.Map, Collection>( + new Collection() + { + new MyEntity { Number = 42 } + }, destination2); + returnCollection.First().Number.ShouldBe(43); + ReferenceEquals(destination2, returnCollection).ShouldBeTrue(); + + // IList + _objectMapper.Map, IList>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var returnIList = _objectMapper.Map, IList>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnIList.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnIList).ShouldBeTrue(); + + // List + _objectMapper.Map, List>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var returnList = _objectMapper.Map, List>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnList.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnList).ShouldBeTrue(); + + // Array + _objectMapper.Map(new MyEntity[] + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var destinationArray = new MyEntityDto2[] + { + new MyEntityDto2 { Number = 40 } + }; + var returnArray = _objectMapper.Map(new MyEntity[] + { + new MyEntity { Number = 42 } + }, destinationArray); + + returnArray.First().Number.ShouldBe(43); + + // array should not be changed. Same as Mapperly. + destinationArray.First().Number.ShouldBe(40); + ReferenceEquals(returnArray, destinationArray).ShouldBeFalse(); + } + + [Fact] + public void Specific_Object_Mapper_Should_Support_Multiple_IObjectMapper_Interfaces() + { + var myEntityDto2 = _objectMapper.Map(new MyEntity { Number = 42 }); + myEntityDto2.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var myEntity = _objectMapper.Map(new MyEntityDto2 { Number = 42 }); + myEntity.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + // IEnumerable + _objectMapper.Map, IEnumerable>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + _objectMapper.Map, IEnumerable>(new List() + { + new MyEntityDto2 { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + } + + [Fact] + public void Should_Use_Destination_Object_Constructor_If_Available() + { + var id = Guid.NewGuid(); + var dto = _objectMapper.Map(new MyEntity { Number = 42, Id = id }); + dto.Key.ShouldBe(id); + dto.No.ShouldBe(42); + } + + [Fact] + public void Should_Use_Destination_Object_MapFrom_Method_If_Available() + { + var id = Guid.NewGuid(); + var dto = new MyEntityDtoWithMappingMethods(); + _objectMapper.Map(new MyEntity { Number = 42, Id = id }, dto); + dto.Key.ShouldBe(id); + dto.No.ShouldBe(42); + } + + [Fact] + public void Should_Use_Source_Object_Method_If_Available_To_Create_New_Object() + { + var id = Guid.NewGuid(); + var entity = _objectMapper.Map(new MyEntityDtoWithMappingMethods { Key = id, No = 42 }); + entity.Id.ShouldBe(id); + entity.Number.ShouldBe(42); + } + + [Fact] + public void Should_Use_Source_Object_Method_If_Available_To_Map_Existing_Object() + { + var id = Guid.NewGuid(); + var entity = new MyEntity(); + _objectMapper.Map(new MyEntityDtoWithMappingMethods { Key = id, No = 42 }, entity); + entity.Id.ShouldBe(id); + entity.Number.ShouldBe(42); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs new file mode 100644 index 0000000000..1aa6288f3e --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs @@ -0,0 +1,68 @@ +using System; +using Riok.Mapperly.Abstractions; +using Shouldly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class MyDIClass +{ + public string Id { get; set; } + + public DateTime Birthday { get; set; } +} + +public class MyDIClassDto +{ + public string Id { get; set; } + + public DateTime Birthday { get; set; } +} + +public class BirthdayCalculatorService : ITransientDependency +{ + public DateTime Birthday => DateTime.Parse("2025-01-01"); +} + +[Mapper] +public partial class MyDIClassMapper : MapperBase +{ + private readonly BirthdayCalculatorService _birthdayCalculatorService; + + public MyDIClassMapper(BirthdayCalculatorService birthdayCalculatorService) + { + _birthdayCalculatorService = birthdayCalculatorService; + } + + public override partial MyDIClassDto Map(MyDIClass source); + + public override partial void Map(MyDIClass source, MyDIClassDto destination); + + public override void AfterMap(MyDIClass source, MyDIClassDto destination) + { + destination.Birthday = _birthdayCalculatorService.Birthday; + } +} + +public class AbpMapperly_Dependency_Injection_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + private readonly BirthdayCalculatorService _birthdayCalculatorService; + + public AbpMapperly_Dependency_Injection_Tests() + { + _objectMapper = GetRequiredService(); + _birthdayCalculatorService = GetRequiredService(); + } + + [Fact] + public void DI_Test() + { + var myClass = new MyDIClass { Id = "1", Birthday = DateTime.Now }; + var myClassDto = _objectMapper.Map(myClass); + myClassDto.Birthday.ShouldBe(_birthdayCalculatorService.Birthday); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs new file mode 100644 index 0000000000..5eeebe6b82 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.DependencyInjection; +using Riok.Mapperly.Abstractions; +using Shouldly; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class MyReverseClass +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +public class MyReverseClassDto +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +[Mapper] +public partial class MyReverseClassMapper : TwoWayMapperBase +{ + public override partial MyReverseClassDto Map(MyReverseClass source); + + public override partial void Map(MyReverseClass source, MyReverseClassDto destination); + + public override partial MyReverseClass ReverseMap(MyReverseClassDto destination); + + public override partial void ReverseMap(MyReverseClassDto destination, MyReverseClass source); + + public override void BeforeReverseMap(MyReverseClassDto destination) + { + destination.Name = "BeforeReverseMap " + destination.Name; + } + + public override void AfterReverseMap(MyReverseClassDto destination, MyReverseClass source) + { + source.Name = destination.Name + " AfterReverseMap"; + } +} + +public class AbpReverseMapperly_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpReverseMapperly_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Map_Test() + { + var myClass = new MyReverseClass { Id = "1", Name = "Test" }; + var myClassDto = _objectMapper.Map(myClass); + myClassDto.Name.ShouldBe("Test"); + + myClass.Id = "2"; + myClass.Name = "Test2"; + + _objectMapper.Map(myClass, myClassDto); + + myClassDto.Id.ShouldBe("2"); + myClassDto.Name.ShouldBe("Test2"); + } + + [Fact] + public void ReverseMap_Test() + { + var myClassDto = new MyReverseClassDto { Id = "1", Name = "Test" }; + var myClass = _objectMapper.Map(myClassDto); + myClass.Name.ShouldBe("BeforeReverseMap Test AfterReverseMap"); + + myClassDto.Id = "2"; + myClassDto.Name = "Test2"; + + _objectMapper.Map(myClassDto, myClass); + + myClass.Id.ShouldBe("2"); + myClass.Name.ShouldBe("BeforeReverseMap Test2 AfterReverseMap"); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs new file mode 100644 index 0000000000..8ff5ca2eb1 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Mapperly; + +[DependsOn( + typeof(AbpMapperlyModule), + typeof(AbpObjectExtendingTestModule) +)] +public class MapperlyTestModule : AbpModule +{ + +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs new file mode 100644 index 0000000000..49be64b61a --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs @@ -0,0 +1,44 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectExtending.TestObjects; + +[Mapper] +public partial class MyEntityMapper : MapperBase +{ + public override partial MyEntityDto Map(MyEntity source); + + public override partial void Map(MyEntity source, MyEntityDto destination); +} + +[Mapper] +public partial class MyEnumMapper : MapperBase +{ + public override partial MyEnumDto Map(MyEnum source); + + public override void Map(MyEnum source, MyEnumDto destination) + { + destination = Map(source); + } +} + +[Mapper] +[MapExtraProperties(IgnoredProperties = ["CityName"])] +public partial class ExtensibleTestPersonMapper : MapperBase +{ + public override partial ExtensibleTestPersonDto Map(ExtensibleTestPerson source); + + public override partial void Map(ExtensibleTestPerson source, ExtensibleTestPersonDto destination); +} + +[Mapper] +[MapExtraProperties(MapToRegularProperties = true)] +public partial class ExtensibleTestPersonWithRegularPropertiesDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Name))] + [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Age))] + [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.IsActive))] + public override partial ExtensibleTestPersonWithRegularPropertiesDto Map(ExtensibleTestPerson source); + + public override partial void Map(ExtensibleTestPerson source, ExtensibleTestPersonWithRegularPropertiesDto destination); +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs new file mode 100644 index 0000000000..f137e476f1 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntity +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs new file mode 100644 index 0000000000..7630b2d14e --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntityDto +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs new file mode 100644 index 0000000000..9d00eff9c4 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntityDto2 +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs new file mode 100644 index 0000000000..bf8c774599 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs @@ -0,0 +1,43 @@ +using System; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly.SampleClasses; + +//TODO: Move tests to Volo.Abp.ObjectMapping test project +public class MyEntityDtoWithMappingMethods : IMapFrom, IMapTo +{ + public Guid Key { get; set; } + + public int No { get; set; } + + public MyEntityDtoWithMappingMethods() + { + + } + + public MyEntityDtoWithMappingMethods(MyEntity entity) + { + MapFrom(entity); + } + + public void MapFrom(MyEntity source) + { + Key = source.Id; + No = source.Number; + } + + MyEntity IMapTo.MapTo() + { + return new MyEntity + { + Id = Key, + Number = No + }; + } + + void IMapTo.MapTo(MyEntity destination) + { + destination.Id = Key; + destination.Number = No; + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs new file mode 100644 index 0000000000..cd73c98747 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs @@ -0,0 +1,39 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntityToMyEntityDto2Mapper : IObjectMapper, IObjectMapper, ITransientDependency +{ + public MyEntityDto2 Map(MyEntity source) + { + return new MyEntityDto2 + { + Id = source.Id, + Number = source.Number + 1 + }; + } + + public MyEntityDto2 Map(MyEntity source, MyEntityDto2 destination) + { + destination.Id = source.Id; + destination.Number = source.Number + 1; + return destination; + } + + public MyEntity Map(MyEntityDto2 source) + { + return new MyEntity + { + Id = source.Id, + Number = source.Number + 1 + }; + } + + public MyEntity Map(MyEntityDto2 source, MyEntity destination) + { + destination.Id = source.Id; + destination.Number = source.Number + 1; + return destination; + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs new file mode 100644 index 0000000000..fe2b72300c --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Mapperly.SampleClasses; + +public enum MyEnum +{ + Value1 = 1, + Value2, + Value3 +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs new file mode 100644 index 0000000000..40d28d0501 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Mapperly.SampleClasses; + +public enum MyEnumDto +{ + Value1 = 2, + Value2, + Value3, + Value +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs new file mode 100644 index 0000000000..9fd2c556fb --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyNotMappedDto +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} 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..90c464cd3b 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,40 @@ [ + { + "version": "9.3.1", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.3.1" + } + }, + { + "version": "9.2.3", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "4.2.3" + } + }, + { + "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/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj index d8c149f8b5..b4530a5dd7 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj @@ -21,6 +21,7 @@ + diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs new file mode 100644 index 0000000000..dcfaf275dd --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationMappers.cs @@ -0,0 +1,21 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Identity; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Account; + +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToProfileDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(ProfileDto.HasPassword))] + public override partial ProfileDto Map(IdentityUser source); + + [MapperIgnoreTarget(nameof(ProfileDto.HasPassword))] + public override partial void Map(IdentityUser source, ProfileDto destination); + + public override void AfterMap(IdentityUser source, ProfileDto destination) + { + destination.HasPassword = source.PasswordHash != null; + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs index 283e06a934..04f25d33d8 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs +++ b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModule.cs @@ -1,4 +1,4 @@ -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Emailing; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -12,7 +12,8 @@ namespace Volo.Abp.Account; typeof(AbpAccountApplicationContractsModule), typeof(AbpIdentityApplicationModule), typeof(AbpUiNavigationModule), - typeof(AbpEmailingModule) + typeof(AbpEmailingModule), + typeof(AbpMapperlyModule) )] public class AbpAccountApplicationModule : AbpModule { @@ -23,11 +24,6 @@ public class AbpAccountApplicationModule : AbpModule options.FileSets.AddEmbedded(); }); - Configure(options => - { - options.AddProfile(validate: true); - }); - Configure(options => { options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword"; diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs b/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs deleted file mode 100644 index 0c117d98d0..0000000000 --- a/modules/account/src/Volo.Abp.Account.Application/Volo/Abp/Account/AbpAccountApplicationModuleAutoMapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Volo.Abp.Identity; - -namespace Volo.Abp.Account; - -public class AbpAccountApplicationModuleAutoMapperProfile : Profile -{ - public AbpAccountApplicationModuleAutoMapperProfile() - { - CreateMap() - .ForMember(dest => dest.HasPassword, - op => op.MapFrom(src => src.PasswordHash != null)) - .MapExtraProperties(); - } -} diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs deleted file mode 100644 index af59d75321..0000000000 --- a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Volo.Abp.Account.Blazor.Pages.Account; -using Volo.Abp.AutoMapper; -using Volo.Abp.Identity; - -namespace Volo.Abp.Account.Blazor; - -public class AbpAccountBlazorAutoMapperProfile : Profile -{ - public AbpAccountBlazorAutoMapperProfile() - { - CreateMap() - .MapExtraProperties() - .Ignore(x => x.PhoneNumberConfirmed) - .Ignore(x => x.EmailConfirmed); - - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs new file mode 100644 index 0000000000..98ef9f9ab6 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorMappers.cs @@ -0,0 +1,27 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Account.Blazor.Pages.Account; +using Volo.Abp.Mapperly; +using Volo.Abp.Identity; + +namespace Volo.Abp.Account.Blazor; + +[Mapper] +[MapExtraProperties] +public partial class ProfileDtoToPersonalInfoModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(PersonalInfoModel.PhoneNumberConfirmed))] + [MapperIgnoreTarget(nameof(PersonalInfoModel.EmailConfirmed))] + public override partial PersonalInfoModel Map(ProfileDto source); + + [MapperIgnoreTarget(nameof(PersonalInfoModel.PhoneNumberConfirmed))] + [MapperIgnoreTarget(nameof(PersonalInfoModel.EmailConfirmed))] + public override partial void Map(ProfileDto source, PersonalInfoModel destination); +} + +[Mapper] +[MapExtraProperties] +public partial class PersonalInfoModelToUpdateProfileDtoMapper : MapperBase +{ + public override partial UpdateProfileDto Map(PersonalInfoModel source); + public override partial void Map(PersonalInfoModel source, UpdateProfileDto destination); +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs index 61b42e1807..6a44f156ee 100644 --- a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs @@ -2,7 +2,7 @@ using Volo.Abp.Account.Blazor.Pages.Account; using Volo.Abp.AspNetCore.Components.Web.Theming; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; @@ -13,21 +13,16 @@ namespace Volo.Abp.Account.Blazor; [DependsOn( typeof(AbpAspNetCoreComponentsWebThemingModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAccountApplicationContractsModule) )] public class AbpAccountBlazorModule : AbpModule { private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner(); - + public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { @@ -39,7 +34,7 @@ public class AbpAccountBlazorModule : AbpModule options.AdditionalAssemblies.Add(typeof(AbpAccountBlazorModule).Assembly); }); } - + public override void PostConfigureServices(ServiceConfigurationContext context) { OneTimeRunner.Run(() => diff --git a/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj b/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj index 0eaa3cd978..7590216d06 100644 --- a/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj +++ b/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj @@ -10,7 +10,7 @@ - + diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index b8ca910eba..9e3fff950b 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -1,4 +1,4 @@ -using IdentityModel; +using Duende.IdentityModel; using IdentityServer4.Events; using IdentityServer4.Models; using IdentityServer4.Services; diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs deleted file mode 100644 index cdc92e8b19..0000000000 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebAutomapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Volo.Abp.Account.Web.Pages.Account; -using Volo.Abp.Identity; -using AutoMapper; -using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; - -namespace Volo.Abp.Account.Web; - -public class AbpAccountWebAutoMapperProfile : Profile -{ - public AbpAccountWebAutoMapperProfile() - { - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs new file mode 100644 index 0000000000..09a2e8373c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebMappers.cs @@ -0,0 +1,13 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Account.Web; + +[Mapper] +[MapExtraProperties] +public partial class ProfileDtoToPersonalInfoModelMapper : MapperBase +{ + public override partial AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel Map(ProfileDto source); + public override partial void Map(ProfileDto source, AccountProfilePersonalInfoManagementGroupViewComponent.PersonalInfoModel destination); +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index 1b41deaff7..f246beed84 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -8,7 +8,7 @@ using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.ExceptionHandling; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; using Volo.Abp.Identity.AspNetCore; @@ -24,14 +24,14 @@ namespace Volo.Abp.Account.Web; [DependsOn( typeof(AbpAccountApplicationContractsModule), typeof(AbpIdentityAspNetCoreModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAspNetCoreMvcUiThemeSharedModule), typeof(AbpExceptionHandlingModule) )] public class AbpAccountWebModule : AbpModule { private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner(); - + public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.PreConfigure(options => @@ -59,11 +59,7 @@ public class AbpAccountWebModule : AbpModule ConfigureProfileManagementPage(); - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { @@ -96,7 +92,7 @@ public class AbpAccountWebModule : AbpModule }); } - + public override void PostConfigureServices(ServiceConfigurationContext context) { OneTimeRunner.Run(() => diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index efedb773fe..3f52eae0a4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -39,7 +39,7 @@ - + diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs index 523dec994f..1f451f58d9 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Claims; -using IdentityModel; +using Duende.IdentityModel; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj index 92c5715372..9dc85442bb 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj @@ -11,7 +11,7 @@ - + diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs index cd83333495..308f26f086 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Projects/Pdf/IText/ITextHtmlToPdfRenderer.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using iText.Html2pdf; using iText.Kernel.Pdf; using iText.Kernel.Pdf.Action; +using iText.Layout.Font; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Docs.Utils; @@ -14,13 +15,13 @@ namespace Volo.Docs.Projects.Pdf.IText; public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer, ITransientDependency { protected IOptions Options { get; } - + public ITextHtmlToPdfRenderer(IOptions options) { Options = options; } - - public virtual Task RenderAsync(string title, string html, List documents) + + public virtual async Task RenderAsync(string title, string html, List documents) { var pdfStream = new MemoryStream(); using (var pdfWriter = new PdfWriter(pdfStream)) @@ -29,33 +30,43 @@ public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer, ITransientDependency using (var pdfDocument = new iText.Kernel.Pdf.PdfDocument(pdfWriter)) { pdfDocument.GetDocumentInfo().SetTitle(title); - CreatePdfFromHtml(html, pdfDocument); - AddOutlinesToPdf(pdfDocument, documents); + await CreatePdfFromHtmlAsync(html, pdfDocument); + await AddOutlinesToPdfAsync(pdfDocument, documents); } } - + pdfStream.Position = 0; - return Task.FromResult(pdfStream); + return pdfStream; } - private void CreatePdfFromHtml(string html, iText.Kernel.Pdf.PdfDocument pdfDocument) + protected virtual async Task CreatePdfFromHtmlAsync(string html, iText.Kernel.Pdf.PdfDocument pdfDocument) { var converter = new ConverterProperties(); + var fontProvider = await GetFontProviderAsync(); + if (fontProvider != null) + { + converter.SetFontProvider(fontProvider); + } var tagWorkerFactory = new HtmlIdTagWorkerFactory(pdfDocument); converter.SetTagWorkerFactory(tagWorkerFactory); - HtmlConverter.ConvertToDocument(html, pdfDocument, converter); - tagWorkerFactory.AddNamedDestinations(); } - private void AddOutlinesToPdf(iText.Kernel.Pdf.PdfDocument pdfDocument, List documents) + protected virtual Task GetFontProviderAsync() + { + return Task.FromResult(null); + } + + protected virtual Task AddOutlinesToPdfAsync(iText.Kernel.Pdf.PdfDocument pdfDocument, List documents) { var pdfOutlines = pdfDocument.GetOutlines(false); BuildPdfOutlines(pdfOutlines, documents); + + return Task.CompletedTask; } - private void BuildPdfOutlines(PdfOutline parentOutline, List pdfDocumentNodes) + protected virtual Task BuildPdfOutlines(PdfOutline parentOutline, List pdfDocumentNodes) { foreach (var pdfDocumentNode in pdfDocumentNodes) { @@ -63,7 +74,7 @@ public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer, ITransientDependency { continue; } - + var outline = parentOutline.AddOutline(pdfDocumentNode.Title); if (!pdfDocumentNode.Id.IsNullOrWhiteSpace()) { @@ -75,5 +86,7 @@ public class ITextHtmlToPdfRenderer : IHtmlToPdfRenderer, ITransientDependency BuildPdfOutlines(outline, pdfDocumentNode.Children); } } + + return Task.CompletedTask; } -} \ 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/feature-management/src/Volo.Abp.FeatureManagement.Installer/AngularInstallationInfo.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Installer/AngularInstallationInfo.json index 5f842bab58..d03ede76ed 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Installer/AngularInstallationInfo.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Installer/AngularInstallationInfo.json @@ -15,8 +15,6 @@ "namespace": "@abp/ng.feature-management" } ], - "ngModuleImports":[ - ], "providerNames":[ "provideFeatureManagementConfig()" ] diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj b/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj index 0b3693b5d1..7125af8060 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj @@ -18,7 +18,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs new file mode 100644 index 0000000000..164da27aae --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationMappers.cs @@ -0,0 +1,19 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Identity; +using Volo.Abp.Mapperly; + +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToIdentityUserDtoMapper : MapperBase +{ + public override partial IdentityUserDto Map(IdentityUser source); + public override partial void Map(IdentityUser source, IdentityUserDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class IdentityRoleToIdentityRoleDtoMapper : MapperBase +{ + public override partial IdentityRoleDto Map(IdentityRole source); + public override partial void Map(IdentityRole source, IdentityRoleDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs index 673a0605c7..07865ab8f5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement; @@ -8,18 +8,13 @@ namespace Volo.Abp.Identity; [DependsOn( typeof(AbpIdentityDomainModule), typeof(AbpIdentityApplicationContractsModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpPermissionManagementApplicationModule) )] public class AbpIdentityApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs deleted file mode 100644 index a546a9bca1..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; - -namespace Volo.Abp.Identity; - -public class AbpIdentityApplicationModuleAutoMapperProfile : Profile -{ - public AbpIdentityApplicationModuleAutoMapperProfile() - { - CreateMap() - .MapExtraProperties(); - - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs deleted file mode 100644 index 52a450785e..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AutoMapper; -using Volo.Abp.AutoMapper; - -namespace Volo.Abp.Identity.Blazor; - -public class AbpIdentityBlazorAutoMapperProfile : Profile -{ - public AbpIdentityBlazorAutoMapperProfile() - { - CreateMap() - .MapExtraProperties() - .Ignore(x => x.Password) - .Ignore(x => x.RoleNames); - - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs new file mode 100644 index 0000000000..614ca6ae32 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMappers.cs @@ -0,0 +1,25 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Identity.Blazor; + +[Mapper] +[MapExtraProperties] +public partial class IdentityUserDtoToIdentityUserUpdateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.Password))] + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial IdentityUserUpdateDto Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.Password))] + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial void Map(IdentityUserDto source, IdentityUserUpdateDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class IdentityRoleDtoToIdentityRoleUpdateDtoMapper : MapperBase +{ + public override partial IdentityRoleUpdateDto Map(IdentityRoleDto source); + public override partial void Map(IdentityRoleDto source, IdentityRoleUpdateDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs index 3367b42a1b..89aba05f89 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs @@ -1,7 +1,7 @@ using Localization.Resources.AbpUi; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.BlazoriseUI; using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; @@ -16,7 +16,7 @@ namespace Volo.Abp.Identity.Blazor; [DependsOn( typeof(AbpIdentityApplicationContractsModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpPermissionManagementBlazorModule), typeof(AbpBlazoriseUIModule) )] @@ -26,12 +26,7 @@ public class AbpIdentityBlazorModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { @@ -42,7 +37,7 @@ public class AbpIdentityBlazorModule : AbpModule { options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorModule).Assembly); }); - + Configure(options => { options.Resources diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index d65325abae..456292fa5c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -9,6 +9,9 @@ @using Volo.Abp.BlazoriseUI.Components.ObjectExtending @using Volo.Abp.AspNetCore.Components.Web.Theming.Layout @inject AbpBlazorMessageLocalizerHelper LH +@using Microsoft.Extensions.Localization +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation @inherits AbpCrudPageBase diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs index d683823461..4e9a6576d1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -41,6 +41,7 @@ public partial class RoleManagement protected override ValueTask SetBreadcrumbItemsAsync() { + BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(LUiNavigation["Menu:Administration"].Value)); BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(L["Menu:IdentityManagement"].Value)); BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(L["Roles"].Value)); return base.SetBreadcrumbItemsAsync(); diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index 9ecb31e9e7..c2c54814ff 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -6,6 +6,9 @@ @using Volo.Abp.Identity.Localization @using Volo.Abp.AspNetCore.Components.Web.Theming.Layout @inject AbpBlazorMessageLocalizerHelper LH +@using Microsoft.Extensions.Localization +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation @inherits AbpCrudPageBase diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs index 1da55397ee..a52474b8b1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs @@ -75,6 +75,7 @@ public partial class UserManagement protected override ValueTask SetBreadcrumbItemsAsync() { + BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(LUiNavigation["Menu:Administration"].Value)); BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(L["Menu:IdentityManagement"].Value)); BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(L["Users"].Value)); return base.SetBreadcrumbItemsAsync(); diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj b/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj index 355cb4294a..777fb1c31e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj @@ -8,7 +8,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj index fadd6e4109..a1bde79665 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj @@ -23,7 +23,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index baf0871c58..590beb8e09 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Modularity; @@ -19,7 +19,7 @@ namespace Volo.Abp.Identity; typeof(AbpDddDomainModule), typeof(AbpIdentityDomainSharedModule), typeof(AbpUsersDomainModule), - typeof(AbpAutoMapperModule) + typeof(AbpMapperlyModule) )] public class AbpIdentityDomainModule : AbpModule { @@ -35,12 +35,7 @@ public class AbpIdentityDomainModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs new file mode 100644 index 0000000000..f7d41c9891 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappers.cs @@ -0,0 +1,33 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity; + +[Mapper] +public partial class IdentityUserToUserEtoMapper : MapperBase +{ + public override partial UserEto Map(IdentityUser source); + public override partial void Map(IdentityUser source, UserEto destination); +} + +[Mapper] +public partial class IdentityClaimTypeToIdentityClaimTypeEtoMapper : MapperBase +{ + public override partial IdentityClaimTypeEto Map(IdentityClaimType source); + public override partial void Map(IdentityClaimType source, IdentityClaimTypeEto destination); +} + +[Mapper] +public partial class IdentityRoleToIdentityRoleEtoMapper : MapperBase +{ + public override partial IdentityRoleEto Map(IdentityRole source); + public override partial void Map(IdentityRole source, IdentityRoleEto destination); +} + +[Mapper] +public partial class OrganizationUnitToOrganizationUnitEtoMapper : MapperBase +{ + public override partial OrganizationUnitEto Map(OrganizationUnit source); + public override partial void Map(OrganizationUnit source, OrganizationUnitEto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs deleted file mode 100644 index ae9e5b5065..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDomainMappingProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Volo.Abp.Users; - -namespace Volo.Abp.Identity; - -public class IdentityDomainMappingProfile : Profile -{ - public IdentityDomainMappingProfile() - { - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs deleted file mode 100644 index e032b3352f..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs +++ /dev/null @@ -1,57 +0,0 @@ -using AutoMapper; -using Volo.Abp.AutoMapper; -using Volo.Abp.Identity.Web.Pages.Identity.Roles; -using CreateUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel; -using EditUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel; - -namespace Volo.Abp.Identity.Web; - -public class AbpIdentityWebAutoMapperProfile : Profile -{ - public AbpIdentityWebAutoMapperProfile() - { - CreateUserMappings(); - CreateRoleMappings(); - } - - protected virtual void CreateUserMappings() - { - //List - CreateMap() - .Ignore(x => x.Password); - - //CreateModal - CreateMap() - .MapExtraProperties() - .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.IsAssigned, opt => opt.Ignore()); - - //EditModal - CreateMap() - .MapExtraProperties() - .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.IsAssigned, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) - .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()); - } - - protected virtual void CreateRoleMappings() - { - //List - CreateMap(); - - //CreateModal - CreateMap() - .MapExtraProperties(); - - //EditModal - CreateMap() - .MapExtraProperties(); - } -} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs new file mode 100644 index 0000000000..1877dac1d8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebMappers.cs @@ -0,0 +1,94 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using Volo.Abp.Identity.Web.Pages.Identity.Roles; +using CreateUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel; +using EditUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel; + +namespace Volo.Abp.Identity.Web; + +[Mapper] +public partial class IdentityUserDtoToEditUserModalModelUserInfoViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(EditUserModalModel.UserInfoViewModel.Password))] + public override partial EditUserModalModel.UserInfoViewModel Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(EditUserModalModel.UserInfoViewModel.Password))] + public override partial void Map(IdentityUserDto source, EditUserModalModel.UserInfoViewModel destination); +} + +[Mapper] +[MapExtraProperties] +public partial class CreateUserModalModelUserInfoViewModelToIdentityUserCreateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserCreateDto.RoleNames))] + public override partial IdentityUserCreateDto Map(CreateUserModalModel.UserInfoViewModel source); + + [MapperIgnoreTarget(nameof(IdentityUserCreateDto.RoleNames))] + public override partial void Map(CreateUserModalModel.UserInfoViewModel source, IdentityUserCreateDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class IdentityRoleDtoToCreateUserModalModelAssignedRoleViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(CreateUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial CreateUserModalModel.AssignedRoleViewModel Map(IdentityRoleDto source); + + [MapperIgnoreTarget(nameof(CreateUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial void Map(IdentityRoleDto source, CreateUserModalModel.AssignedRoleViewModel destination); +} + +[Mapper] +public partial class EditUserModalModelUserInfoViewModelToIdentityUserUpdateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial IdentityUserUpdateDto Map(EditUserModalModel.UserInfoViewModel source); + + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial void Map(EditUserModalModel.UserInfoViewModel source, IdentityUserUpdateDto destination); +} + +[Mapper] +public partial class IdentityRoleDtoToEditUserModalModelAssignedRoleViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(EditUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial EditUserModalModel.AssignedRoleViewModel Map(IdentityRoleDto source); + + [MapperIgnoreTarget(nameof(EditUserModalModel.AssignedRoleViewModel.IsAssigned))] + public override partial void Map(IdentityRoleDto source, EditUserModalModel.AssignedRoleViewModel destination); +} + +[Mapper] +public partial class IdentityUserDtoToEditUserModalModelDetailViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.CreatedBy))] + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.ModifiedBy))] + public override partial EditUserModalModel.DetailViewModel Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.CreatedBy))] + [MapperIgnoreTarget(nameof(EditUserModalModel.DetailViewModel.ModifiedBy))] + public override partial void Map(IdentityUserDto source, EditUserModalModel.DetailViewModel destination); +} + +[Mapper] +public partial class IdentityRoleDtoToEditModalModelRoleInfoModelMapper : MapperBase +{ + public override partial EditModalModel.RoleInfoModel Map(IdentityRoleDto source); + public override partial void Map(IdentityRoleDto source, EditModalModel.RoleInfoModel destination); +} + +[Mapper] +[MapExtraProperties] +public partial class CreateModalModelRoleInfoModelToIdentityRoleCreateDtoMapper : MapperBase +{ + public override partial IdentityRoleCreateDto Map(CreateModalModel.RoleInfoModel source); + public override partial void Map(CreateModalModel.RoleInfoModel source, IdentityRoleCreateDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class EditModalModelRoleInfoModelToIdentityRoleUpdateDtoMapper : MapperBase +{ + public override partial IdentityRoleUpdateDto Map(EditModalModel.RoleInfoModel source); + public override partial void Map(EditModalModel.RoleInfoModel source, IdentityRoleUpdateDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index ae3e8ebc72..6d2faf6467 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.PageToolbars; -using Volo.Abp.AutoMapper; +using Volo.Abp.Mapperly; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; using Volo.Abp.Identity.Localization; using Volo.Abp.Identity.Web.Navigation; @@ -19,7 +19,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.Identity.Web; [DependsOn(typeof(AbpIdentityApplicationContractsModule))] -[DependsOn(typeof(AbpAutoMapperModule))] +[DependsOn(typeof(AbpMapperlyModule))] [DependsOn(typeof(AbpPermissionManagementWebModule))] [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] public class AbpIdentityWebModule : AbpModule @@ -51,12 +51,7 @@ public class AbpIdentityWebModule : AbpModule options.FileSets.AddEmbedded(); }); - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml index ca58ce4b84..5b2c767228 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml @@ -7,12 +7,15 @@ @using Volo.Abp.Identity.Localization @using Volo.Abp.Identity.Web.Navigation @using Volo.Abp.Identity.Web.Pages.Identity.Roles +@using Volo.Abp.UI.Navigation.Localization.Resource @model IndexModel @inject IHtmlLocalizer L +@inject IHtmlLocalizer LUiNavigation @inject IAuthorizationService Authorization @inject IPageLayout PageLayout @{ PageLayout.Content.Title = L["Roles"].Value; + PageLayout.Content.BreadCrumb.Add(LUiNavigation["Menu:Administration"].Value); PageLayout.Content.BreadCrumb.Add(L["Menu:IdentityManagement"].Value); PageLayout.Content.MenuItemName = IdentityMenuNames.Roles; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml index 1b9cce4919..31b3751cbd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml @@ -7,12 +7,15 @@ @using Volo.Abp.Identity.Localization @using Volo.Abp.Identity.Web.Navigation @using Volo.Abp.Identity.Web.Pages.Identity.Users +@using Volo.Abp.UI.Navigation.Localization.Resource @model IndexModel @inject IHtmlLocalizer L +@inject IHtmlLocalizer LUiNavigation @inject IAuthorizationService Authorization @inject IPageLayout PageLayout @{ PageLayout.Content.Title = L["Users"].Value; + PageLayout.Content.BreadCrumb.Add(LUiNavigation["Menu:Administration"].Value); PageLayout.Content.BreadCrumb.Add(L["Menu:IdentityManagement"].Value); PageLayout.Content.MenuItemName = IdentityMenuNames.Users; } diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index 8a77024b52..54bd5fd28b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -40,7 +40,7 @@ - + diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/OpenIddict.Demo.Client.BlazorWASM.csproj b/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/OpenIddict.Demo.Client.BlazorWASM.csproj index 05f4b7ddab..5b22c22ac9 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/OpenIddict.Demo.Client.BlazorWASM.csproj +++ b/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/OpenIddict.Demo.Client.BlazorWASM.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/Program.cs b/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/Program.cs index a2bd2c85e3..11da671980 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Client.BlazorWASM/Program.cs @@ -1,4 +1,4 @@ -using IdentityModel; +using Duende.IdentityModel; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using OpenIddict.Demo.Client.BlazorWASM; diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.Console/OpenIddict.Demo.Client.Console.csproj b/modules/openiddict/app/OpenIddict.Demo.Client.Console/OpenIddict.Demo.Client.Console.csproj index 153955d3ad..c4ba8e6011 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Console/OpenIddict.Demo.Client.Console.csproj +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Console/OpenIddict.Demo.Client.Console.csproj @@ -8,7 +8,7 @@ - + diff --git a/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs b/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs index 2e55cc24b1..a17e4ce5df 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs @@ -1,6 +1,6 @@ using System.Net.Http.Headers; using System.Text.Json; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; const string email = "admin@abp.io"; @@ -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/OpenIddict.Demo.Client.Mvc.csproj b/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddict.Demo.Client.Mvc.csproj index 981a815659..f0ab305f3e 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddict.Demo.Client.Mvc.csproj +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddict.Demo.Client.Mvc.csproj @@ -7,7 +7,7 @@ - + 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.Client.Mvc/Program.cs b/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs index 3f9afcfcbc..c0801d8635 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs @@ -1,4 +1,4 @@ -using IdentityModel; +using Duende.IdentityModel; using OpenIddict.Demo.Client.Mvc; var builder = WebApplication.CreateBuilder(args); 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/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs index 1e7e69d4c3..56d01956e4 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs @@ -1,6 +1,5 @@ using Localization.Resources.AbpUi; using Volo.Abp.AspNetCore.Components.Web.Theming; -using Volo.Abp.AutoMapper; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.Localization; @@ -9,7 +8,6 @@ namespace Volo.Abp.PermissionManagement.Blazor; [DependsOn( typeof(AbpAspNetCoreComponentsWebThemingModule), - typeof(AbpAutoMapperModule), typeof(AbpPermissionManagementApplicationContractsModule) )] public class AbpPermissionManagementBlazorModule : AbpModule diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj index 4983eaa9f8..1506afa4eb 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj @@ -9,7 +9,6 @@ - diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebAutoMapperProfile.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebAutoMapperProfile.cs deleted file mode 100644 index f9baa8befd..0000000000 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebAutoMapperProfile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AutoMapper; -using Volo.Abp.AutoMapper; -using Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement; - -namespace Volo.Abp.PermissionManagement.Web; - -public class AbpPermissionManagementWebAutoMapperProfile : Profile -{ - public AbpPermissionManagementWebAutoMapperProfile() - { - CreateMap().Ignore(p => p.IsAllPermissionsGranted); - - CreateMap() - .ForMember(p => p.Depth, opts => opts.Ignore()); - - CreateMap(); - } -} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebMappers.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebMappers.cs new file mode 100644 index 0000000000..950a3972f0 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebMappers.cs @@ -0,0 +1,33 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using static Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement.PermissionManagementModal; + +namespace Volo.Abp.PermissionManagement.Web; + +[Mapper] +public partial class PermissionGroupDtoToPermissionGroupViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(PermissionGroupViewModel.IsAllPermissionsGranted))] + public override partial PermissionGroupViewModel Map(PermissionGroupDto source); + + [MapperIgnoreTarget(nameof(PermissionGroupViewModel.IsAllPermissionsGranted))] + public override partial void Map(PermissionGroupDto source, PermissionGroupViewModel destination); +} + +[Mapper] +public partial class PermissionGrantInfoDtoToPermissionGrantInfoViewModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(PermissionGrantInfoViewModel.Depth))] + public override partial PermissionGrantInfoViewModel Map(PermissionGrantInfoDto source); + + [MapperIgnoreTarget(nameof(PermissionGrantInfoViewModel.Depth))] + public override partial void Map(PermissionGrantInfoDto source, PermissionGrantInfoViewModel destination); +} + +[Mapper] +public partial class ProviderInfoDtoToProviderInfoViewModelMapper : MapperBase +{ + public override partial ProviderInfoViewModel Map(ProviderInfoDto source); + + public override partial void Map(ProviderInfoDto source, ProviderInfoViewModel destination); +} \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs index 5219fafbbf..2159a6107c 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/AbpPermissionManagementWebModule.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; -using Volo.Abp.AutoMapper; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.Localization; using Volo.Abp.VirtualFileSystem; @@ -11,7 +11,7 @@ namespace Volo.Abp.PermissionManagement.Web; [DependsOn(typeof(AbpPermissionManagementApplicationContractsModule))] [DependsOn(typeof(AbpAspNetCoreMvcUiBootstrapModule))] -[DependsOn(typeof(AbpAutoMapperModule))] +[DependsOn(typeof(AbpMapperlyModule))] public class AbpPermissionManagementWebModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) @@ -34,11 +34,7 @@ public class AbpPermissionManagementWebModule : AbpModule options.FileSets.AddEmbedded(); }); - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj index 1c80dda96c..9f58d70303 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Volo.Abp.PermissionManagement.Web.csproj @@ -31,7 +31,7 @@ - + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/AbpSettingManagementBlazorModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/AbpSettingManagementBlazorModule.cs index d8e54353e1..f76b5fdb26 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/AbpSettingManagementBlazorModule.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/AbpSettingManagementBlazorModule.cs @@ -2,8 +2,8 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Components.Web.Theming; using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AutoMapper; using Volo.Abp.Localization; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Blazor.Menus; using Volo.Abp.SettingManagement.Blazor.Settings; @@ -13,7 +13,7 @@ using Volo.Abp.UI.Navigation; namespace Volo.Abp.SettingManagement.Blazor; [DependsOn( - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAspNetCoreComponentsWebThemingModule), typeof(AbpSettingManagementApplicationContractsModule) )] @@ -21,12 +21,7 @@ public class AbpSettingManagementBlazorModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddAutoMapperObjectMapper(); - - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); Configure(options => { diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor index bd82a74805..e6accb0918 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor @@ -4,7 +4,9 @@ @using Volo.Abp.Features @attribute [Authorize] @attribute [RequiresFeature(SettingManagementFeatures.Enable)] - +@using Microsoft.Extensions.Localization +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation @* ************************* PAGE HEADER ************************* *@ diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor.cs index 9eb0ca977a..123ff940c5 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Pages/SettingManagement/SettingManagement.razor.cs @@ -31,7 +31,8 @@ public partial class SettingManagement protected async override Task OnInitializedAsync() { - BreadcrumbItems.Add(new BreadcrumbItem(@L["Settings"])); + BreadcrumbItems.Add(new BreadcrumbItem(LUiNavigation["Menu:Administration"].Value)); + BreadcrumbItems.Add(new BreadcrumbItem(@L["Menu:Settings"].Value)); SettingComponentCreationContext = new SettingComponentCreationContext(ServiceProvider); diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/SettingManagementBlazorAutoMapperProfile.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/SettingManagementBlazorAutoMapperProfile.cs deleted file mode 100644 index 604996e478..0000000000 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/SettingManagementBlazorAutoMapperProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Volo.Abp.SettingManagement.Blazor.Pages.SettingManagement.EmailSettingGroup; - -namespace Volo.Abp.SettingManagement.Blazor; - -public class SettingManagementBlazorAutoMapperProfile : Profile -{ - public SettingManagementBlazorAutoMapperProfile() - { - CreateMap(); - CreateMap(); - - CreateMap(); - } -} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/SettingManagementBlazorMappers.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/SettingManagementBlazorMappers.cs new file mode 100644 index 0000000000..d5364d8e4d --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/SettingManagementBlazorMappers.cs @@ -0,0 +1,31 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using static Volo.Abp.SettingManagement.Blazor.Pages.SettingManagement.EmailSettingGroup.EmailSettingGroupViewComponent; + +namespace Volo.Abp.SettingManagement.Blazor; + +[Mapper] +public partial class UpdateEmailSettingsViewModelToUpdateEmailSettingsDtoMapper : MapperBase +{ + public override partial UpdateEmailSettingsDto Map(UpdateEmailSettingsViewModel source); + + public override partial void Map(UpdateEmailSettingsViewModel source, UpdateEmailSettingsDto destination); +} + + +[Mapper] +public partial class EmailSettingsDtoToUpdateEmailSettingsViewModelMapper : MapperBase +{ + public override partial UpdateEmailSettingsViewModel Map(EmailSettingsDto source); + + public override partial void Map(EmailSettingsDto source, UpdateEmailSettingsViewModel destination); +} + +[Mapper] +public partial class SendTestEmailViewModelToSendTestEmailInputMapper : MapperBase +{ + public override partial SendTestEmailInput Map(SendTestEmailViewModel source); + + public override partial void Map(SendTestEmailViewModel source, SendTestEmailInput destination); +} + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Volo.Abp.SettingManagement.Blazor.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Volo.Abp.SettingManagement.Blazor.csproj index 61045cab96..e045dbf4bb 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Volo.Abp.SettingManagement.Blazor.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor/Volo.Abp.SettingManagement.Blazor.csproj @@ -9,7 +9,7 @@ - + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ar.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ar.json index ec35f5286f..44077ac44f 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ar.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ar.json @@ -17,6 +17,7 @@ "SentSuccessfully": "أرسلت بنجاح", "MailSendingFailed": "فشل إرسال البريد ، يرجى التحقق من تكوين البريد الإلكتروني الخاص بك والمحاولة مرة أخرى.", "Send": "يرسل", + "Menu:Settings": "الإعدادات", "Menu:Emailing": "إرسال بالبريد الإلكتروني", "Menu:TimeZone": "وحدة زمنية", "DisplayName:Timezone": "وحدة زمنية", @@ -36,6 +37,6 @@ "Feature:SettingManagementEnableDescription": "تفعيل إعداد نظام الإدارة في التطبيق.", "Feature:AllowChangingEmailSettings": "السماح لتغيير إعدادات البريد الإلكتروني.", "Feature:AllowChangingEmailSettingsDescription": "السماح لتغيير إعدادات البريد الإلكتروني.", - "SmtpPasswordPlaceholder": "أدخل قيمة لتحديث كلمة المرور", + "SmtpPasswordPlaceholder": "أدخل قيمة لتحديث كلمة المرور" } } \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/cs.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/cs.json index cd9d4d78d1..94a6e1f6ea 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/cs.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/cs.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Úspěšně odesláno", "MailSendingFailed": "Odesílání e-mailu se nezdařilo. Zkontrolujte konfiguraci e-mailu a zkuste to znovu.", "Send": "Poslat", + "Menu:Settings": "Nastavení", "Menu:Emailing": "Zasílání e-mailem", "Menu:TimeZone": "Časové Pásmo", "DisplayName:Timezone": "Časové pásmo", @@ -36,6 +37,6 @@ "Feature:SettingManagementEnableDescription": "Povolit systém správy nastavení v aplikaci.", "Feature:AllowChangingEmailSettings": "Povolit změnu nastavení e-mailu.", "Feature:AllowChangingEmailSettingsDescription": "Povolit změnu nastavení e-mailu.", - "SmtpPasswordPlaceholder": "Zadejte hodnotu pro aktualizaci hesla", + "SmtpPasswordPlaceholder": "Zadejte hodnotu pro aktualizaci hesla" } } \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de-DE.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de-DE.json index 6e17d1c0dc..a63b7201f2 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de-DE.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de-DE.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Erfolgreich gesendet", "MailSendingFailed": "E-Mail-Versand fehlgeschlagen. Bitte überprüfen Sie Ihre E-Mail-Konfiguration und versuchen Sie es erneut.", "Send": "Senden", + "Menu:Settings": "Einstellungen", "Menu:Emailing": "E-Mail", "Menu:TimeZone": "Zeitzone", "DisplayName:Timezone": "Zeitzone", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de.json index 7f8debc508..e78aa19fca 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/de.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Erfolgreich gesendet", "MailSendingFailed": "E-Mail-Versand fehlgeschlagen. Bitte überprüfen Sie Ihre E-Mail-Konfiguration und versuchen Sie es erneut.", "Send": "Schicken", + "Menu:Settings": "Einstellungen", "Menu:Emailing": "E-Mail senden", "Menu:TimeZone": "Zeitzone", "DisplayName:Timezone": "Zeitzone", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/el.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/el.json index 611cfb407d..424e38e8de 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/el.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/el.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Στάλθηκε με επιτυχία", "MailSendingFailed": "Αποτυχία αποστολής email. Ελέγξτε τη διαμόρφωση του email σας και δοκιμάστε ξανά.", "Send": "Αποστολή", + "Menu:Settings": "Ρυθμίσεις", "Menu:Emailing": "Αποστολή email", "SmtpHost": "Διακομιστής", "SmtpPort": "Πόρτα", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json index 84933fe44b..832163574b 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Sent successfully", "MailSendingFailed": "Mail sending failed, please check your email configuration and try again.", "Send": "Send", + "Menu:Settings": "Settings", "Menu:Emailing": "Emailing", "Menu:TimeZone": "Time Zone", "DisplayName:Timezone": "Time zone", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/es.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/es.json index bfc0ab6a3b..91cc71d532 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/es.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/es.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Enviado exitosamente", "MailSendingFailed": "Error al enviar el correo, por favor revise su configuración de correo y vuelva a intentarlo.", "Send": "Enviar", + "Menu:Settings": "Configuraciones", "Menu:Emailing": "Configuración", "Menu:TimeZone": "Zona Horaria", "DisplayName:Timezone": "Zona horaria", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fa.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fa.json index 1a07a34adf..800ac04a66 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fa.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fa.json @@ -5,6 +5,7 @@ "SavedSuccessfully": "با موفقیت ذخیره شد", "Permission:SettingManagement": "مدیریت تنظیمات", "Permission:Emailing": "تنظیمات ایمیل", + "Menu:Settings": "تنظیمات", "Menu:Emailing": "تنظیمات ایمیل", "SmtpHost": "هاست", "SmtpPort": "پورت", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json index f9b52baf2b..8e7efcb8c6 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fi.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Lähetetty onnistuneesti", "MailSendingFailed": "Sähköpostin lähetys epäonnistui. Tarkista sähköpostiasetuksesi ja yritä uudelleen.", "Send": "Lähetä", + "Menu:Settings": "Asetukset", "Menu:Emailing": "Sähköpostiviestit", "Menu:TimeZone": "Aikavyöhyke", "DisplayName:Timezone": "Aikavyöhyke", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fr.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fr.json index 758e9a090c..1844cc3fbb 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fr.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/fr.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Envoyé avec succès", "MailSendingFailed": "L'envoi de courrier a échoué, veuillez vérifier votre configuration de courrier électronique et réessayer.", "Send": "Envoyer", + "Menu:Settings": "Paramètres", "Menu:Emailing": "Envoi par e-mail", "Menu:TimeZone": "Fuseau Horaire", "DisplayName:Timezone": "Fuseau horaire", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hi.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hi.json index 8dacb200e4..8c00ffca0d 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hi.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hi.json @@ -17,6 +17,7 @@ "SentSuccessfully": "सफलतापूर्वक भेज दिया गया", "MailSendingFailed": "मेल भेजने में विफल, कृपया अपनी ईमेल विन्यास की जाँच करें और पुनः प्रयास करें।", "Send": "भेजना", + "Menu:Settings": "समायोजन", "Menu:Emailing": "ईमेल से भेजना", "Menu:TimeZone": "समय क्षेत्र", "DisplayName:Timezone": "समय क्षेत्र", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hr.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hr.json index 133233a10f..c0979707a4 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hr.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hr.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Poslan uspješno", "MailSendingFailed": "Slanje e-pošte nije uspjelo, provjerite konfiguraciju e-pošte i pokušajte ponovo.", "Send": "Poslati", + "Menu:Settings": "Postavke", "Menu:Emailing": "Slanje e-poštom", "Menu:TimeZone": "Vremenska Zona", "DisplayName:Timezone": "Vremenska zona", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hu.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hu.json index 4aed2b5688..efe7876d8d 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hu.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/hu.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Sikeresen elküldve", "MailSendingFailed": "Az e-mail küldése sikertelen, ellenőrizze az e-mail konfigurációját, és próbálja újra.", "Send": "Küld", + "Menu:Settings": "Beállítások", "Menu:Emailing": "E-mailezés", "Menu:TimeZone": "Időzóna", "DisplayName:Timezone": "Időzóna", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/is.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/is.json index 2106b156bc..73c3c736a9 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/is.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/is.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Tókst að senda", "MailSendingFailed": "Tölvupóstur sendist ekki. Athugaðu tölvupóst stillingar þínar og reyndu aftur.", "Send": "Senda", + "Menu:Settings": "Stillingar", "Menu:Emailing": "Senda tölvupóst", "Menu:TimeZone": "Tímabelti", "DisplayName:Timezone": "Tímabelti", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/it.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/it.json index 487078c449..cc6669e7c8 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/it.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/it.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Inviato con successo", "MailSendingFailed": "Invio della posta fallito, controlla la tua configurazione email e riprova.", "Send": "Inviare", + "Menu:Settings": "Impostazioni", "Menu:Emailing": "Invio di e-mail", "Menu:TimeZone": "Fuso Orario", "DisplayName:Timezone": "Fuso orario", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/nl.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/nl.json index 06f07346e5..f6d86ab9ba 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/nl.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/nl.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Succesvol verzonden", "MailSendingFailed": "E-mail verzenden mislukt. Controleer uw e-mailconfiguratie en probeer het opnieuw.", "Send": "Versturen", + "Menu:Settings": "Instellingen", "Menu:Emailing": "E-mail", "Menu:TimeZone": "Tijdzone", "DisplayName:Timezone": "Tijdzone", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json index 35038ee043..45c144626d 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Wysłano pomyślnie", "MailSendingFailed": "Wysyłanie e-maila nie powiodło się. Sprawdź konfigurację e-maila i spróbuj ponownie.", "Send": "Wysłać", + "Menu:Settings": "Ustawienia", "Menu:Emailing": "Wysyłanie e-maili", "Menu:TimeZone": "Strefa Czasowa", "DisplayName:Timezone": "Strefa czasowa", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pt-BR.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pt-BR.json index 9a8cf0317b..831d54707e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pt-BR.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pt-BR.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Enviado com sucesso", "MailSendingFailed": "Falha no envio de e-mail, verifique sua configuração de e-mail e tente novamente.", "Send": "Enviar", + "Menu:Settings": "Configurações", "Menu:Emailing": "Enviando por e-mail", "Menu:TimeZone": "Fuso Horário", "DisplayName:Timezone": "Fuso horário", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ro-RO.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ro-RO.json index 8029a43734..a0f94747ed 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ro-RO.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ro-RO.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Trimis cu succes", "MailSendingFailed": "Trimiterea e-mailului a eșuat. Verificați configurația e-mailului și încercați din nou.", "Send": "Trimite", + "Menu:Settings": "Setări", "Menu:Emailing": "Emailing", "Menu:TimeZone": "Fus Orar", "DisplayName:Timezone": "Fus orar", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ru.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ru.json index 0ab1535a3b..1145595e8e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ru.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/ru.json @@ -17,6 +17,7 @@ "SentSuccessfully": "отправлено успешно", "MailSendingFailed": "Не удалось отправить письмо. Пожалуйста, проверьте настройки электронной почты и повторите попытку.", "Send": "Отправлять", + "Menu:Settings": "Настройки", "Menu:Emailing": "Отправка по электронной почте", "Menu:TimeZone": "Часовой пояс", "DisplayName:Timezone": "Часовой пояс", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sk.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sk.json index 1c68a43ae5..e8661a3471 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sk.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sk.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Úspešne odoslané", "MailSendingFailed": "Odoslanie emailu zlyhalo. Skontrolujte konfiguráciu emailu a skúste to znova.", "Send": "Odoslať", + "Menu:Settings": "Nastavenia", "Menu:Emailing": "Posielanie emailov", "Menu:TimeZone": "Časové Pásmo", "DisplayName:Timezone": "Časové pásmo", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sl.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sl.json index 4805b28994..9bd877053b 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sl.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sl.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Poslano uspešno", "MailSendingFailed": "Pošiljanje e-pošte ni uspelo, preverite konfiguracijo e-pošte in poskusite znova.", "Send": "Pošlji", + "Menu:Settings": "Nastavitve", "Menu:Emailing": "Pošiljanje po e-pošti", "Menu:TimeZone": "Časovni Pas", "DisplayName:Timezone": "Časovni pas", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sv.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sv.json index ebbdad2b32..01719059cc 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sv.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/sv.json @@ -16,6 +16,7 @@ "TestEmailBody": "Testa e-postmeddelandets brödtext här", "SentSuccessfully": "Skickat framgångsrikt", "Send": "Skicka", + "Menu:Settings": "Inställningar", "Menu:Emailing": "E-post", "Menu:TimeZone": "Tidszon", "DisplayName:Timezone": "Tidszon", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/tr.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/tr.json index 3b7fc2ac5f..ff4107e21b 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/tr.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/tr.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Gönderildi", "MailSendingFailed": "E-posta gönderme başarısız, lütfen e-posta yapılandırmanızı kontrol edin ve tekrar deneyin.", "Send": "Gönder", + "Menu:Settings": "Ayarlar", "Menu:Emailing": "Email", "Menu:TimeZone": "Zaman Dilimi", "DisplayName:Timezone": "Zaman dilimi", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/vi.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/vi.json index a9cc466fdb..38b284a6ca 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/vi.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/vi.json @@ -17,6 +17,7 @@ "SentSuccessfully": "Gửi thành công", "MailSendingFailed": "Gửi email thất bại. Vui lòng kiểm tra cấu hình email của bạn và thử lại.", "Send": "Gửi", + "Menu:Settings": "Cài đặt", "Menu:Emailing": "Gửi email", "Menu:TimeZone": "Múi Giờ", "DisplayName:Timezone": "Múi giờ", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hans.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hans.json index 3aad0b9e8a..b9d0940273 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hans.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hans.json @@ -17,6 +17,7 @@ "SentSuccessfully": "发送成功", "MailSendingFailed": "邮件发送失败,请检查您的电子邮件配置并重试。", "Send": "发送", + "Menu:Settings": "设置", "Menu:Emailing": "邮件", "Menu:TimeZone": "时区", "DisplayName:Timezone": "时区", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hant.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hant.json index 77c586efe4..b57a542842 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hant.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/zh-Hant.json @@ -17,6 +17,7 @@ "SentSuccessfully": "發送成功", "MailSendingFailed": "郵件發送失敗,請檢查你的郵件配置並重試.", "Send": "發送", + "Menu:Settings": "設置", "Menu:Emailing": "信箱", "Menu:TimeZone": "時區", "DisplayName:Timezone": "時區", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json index 987f8f72ea..bbbdcb0224 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json @@ -4,7 +4,7 @@ "name": "@abp/ng.setting-management", "appRoutingModuleConfiguration":{ "routes":[ - "{ path: 'setting-management', loadChildren: () => import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()),}" + "{ path: 'setting-management', loadChildren: () => import('@abp/ng.setting-management').then(c => c.createRoutes()),}" ] }, "appModuleConfiguration":{ @@ -16,8 +16,6 @@ "namespace": "@abp/ng.setting-management/config" } ], - "ngModuleImports":[ - ], "providerNames":[ "provideSettingManagementConfig()" ] diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/AbpSettingManagementWebModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/AbpSettingManagementWebModule.cs index b5d3df17ad..1bc204f316 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/AbpSettingManagementWebModule.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/AbpSettingManagementWebModule.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; -using Volo.Abp.AutoMapper; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; +using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Localization; using Volo.Abp.SettingManagement.Web.Navigation; @@ -15,7 +15,7 @@ namespace Volo.Abp.SettingManagement.Web; [DependsOn( typeof(AbpSettingManagementApplicationContractsModule), - typeof(AbpAutoMapperModule), + typeof(AbpMapperlyModule), typeof(AbpAspNetCoreMvcUiThemeSharedModule), typeof(AbpSettingManagementDomainSharedModule) )] @@ -58,10 +58,6 @@ public class AbpSettingManagementWebModule : AbpModule options.DisableModule(SettingManagementRemoteServiceConsts.ModuleName); }); - context.Services.AddAutoMapperObjectMapper(); - Configure(options => - { - options.AddProfile(validate: true); - }); + context.Services.AddMapperlyObjectMapper(); } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml index d31670a036..b259131ec7 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Index.cshtml @@ -4,11 +4,15 @@ @using Volo.Abp.SettingManagement.Localization @using Volo.Abp.SettingManagement.Web.Navigation @using Volo.Abp.SettingManagement.Web.Pages.SettingManagement +@using Volo.Abp.UI.Navigation.Localization.Resource @model IndexModel @inject IHtmlLocalizer L +@inject IHtmlLocalizer LUiNavigation @inject IPageLayout PageLayout @{ PageLayout.Content.Title = L["Settings"].Value; + + PageLayout.Content.BreadCrumb.Add(LUiNavigation["Menu:Administration"].Value); PageLayout.Content.MenuItemName = SettingManagementMenuNames.GroupName; } @section scripts { diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/SettingManagementWebAutoMapperProfile.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/SettingManagementWebAutoMapperProfile.cs deleted file mode 100644 index 77afa99224..0000000000 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/SettingManagementWebAutoMapperProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using Volo.Abp.SettingManagement.Web.Pages.SettingManagement.Components.EmailSettingGroup; - -namespace Volo.Abp.SettingManagement.Web; - -public class SettingManagementWebAutoMapperProfile : Profile -{ - public SettingManagementWebAutoMapperProfile() - { - CreateMap(); - - CreateMap(); - } -} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/SettingManagementWebMappers.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/SettingManagementWebMappers.cs new file mode 100644 index 0000000000..8a205919c9 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/SettingManagementWebMappers.cs @@ -0,0 +1,22 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using static Volo.Abp.SettingManagement.Web.Pages.SettingManagement.Components.EmailSettingGroup.EmailSettingGroupViewComponent; +using static Volo.Abp.SettingManagement.Web.Pages.SettingManagement.Components.EmailSettingGroup.SendTestEmailModal; + +namespace Volo.Abp.SettingManagement.Web; + +[Mapper] +public partial class EmailSettingsDtoToUpdateEmailSettingsViewModelMapper : MapperBase +{ + public override partial UpdateEmailSettingsViewModel Map(EmailSettingsDto source); + + public override partial void Map(EmailSettingsDto source, UpdateEmailSettingsViewModel destination); +} + +[Mapper] +public partial class SendTestEmailViewModelToSendTestEmailInputMapper : MapperBase +{ + public override partial SendTestEmailInput Map(SendTestEmailViewModel source); + + public override partial void Map(SendTestEmailViewModel source, SendTestEmailInput destination); +} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj index 182fbb46cd..49fe6fe0b2 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Volo.Abp.SettingManagement.Web.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor index 478adfb0eb..f87467f8c2 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor @@ -8,6 +8,9 @@ @using Volo.Abp.BlazoriseUI.Components.ObjectExtending @using Volo.Abp.AspNetCore.Components.Web @inject AbpBlazorMessageLocalizerHelper LH +@using Microsoft.Extensions.Localization +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation @inherits AbpCrudPageBase diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs index cea9267f00..272bd9cf7a 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs @@ -41,6 +41,7 @@ public partial class TenantManagement protected override ValueTask SetBreadcrumbItemsAsync() { + BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(LUiNavigation["Menu:Administration"])); BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(L["Menu:TenantManagement"])); BreadcrumbItems.Add(new BlazoriseUI.BreadcrumbItem(L["Tenants"])); return base.SetBreadcrumbItemsAsync(); 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/modules/tenant-management/src/Volo.Abp.TenantManagement.Installer/AngularInstallationInfo.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Installer/AngularInstallationInfo.json index 700380b9ca..029e846026 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Installer/AngularInstallationInfo.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Installer/AngularInstallationInfo.json @@ -4,7 +4,7 @@ "name": "@abp/ng.tenant-management", "appRoutingModuleConfiguration":{ "routes":[ - "{ path: 'tenant-management', loadChildren: () => import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()),}" + "{ path: 'tenant-management', loadChildren: () => import('@abp/ng.tenant-management').then(c => c.createRoutes()),}" ] }, "appModuleConfiguration":{ @@ -16,8 +16,6 @@ "namespace": "@abp/ng.tenant-management/config" } ], - "ngModuleImports":[ - ], "providerNames":[ "provideTenantManagementConfig()" ] diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml index 57859e28d3..8a17555bd9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml @@ -8,12 +8,15 @@ @using Volo.Abp.TenantManagement.Localization @using Volo.Abp.TenantManagement.Web.Navigation @using Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants +@using Volo.Abp.UI.Navigation.Localization.Resource @model IndexModel @inject IHtmlLocalizer L +@inject IHtmlLocalizer LUiNavigation @inject IAuthorizationService Authorization @inject IPageLayout PageLayout @{ PageLayout.Content.Title = L["Tenants"].Value; + PageLayout.Content.BreadCrumb.Add(LUiNavigation["Menu:Administration"].Value); PageLayout.Content.BreadCrumb.Add(L["Menu:TenantManagement"].Value); PageLayout.Content.MenuItemName = TenantManagementMenuNames.Tenants; } diff --git a/npm/ng-packs/apps/dev-app/project.json b/npm/ng-packs/apps/dev-app/project.json index 1213acbb2f..958cdfb3ee 100644 --- a/npm/ng-packs/apps/dev-app/project.json +++ b/npm/ng-packs/apps/dev-app/project.json @@ -6,13 +6,13 @@ "prefix": "app", "targets": { "build": { - "executor": "@angular-devkit/build-angular:browser", + "executor": "@angular-devkit/build-angular:application", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/apps/dev-app", "index": "apps/dev-app/src/index.html", - "main": "apps/dev-app/src/main.ts", - "polyfills": "apps/dev-app/src/polyfills.ts", + "browser": "apps/dev-app/src/main.ts", + "polyfills": ["apps/dev-app/src/polyfills.ts"], "tsConfig": "apps/dev-app/tsconfig.app.json", "inlineStyleLanguage": "scss", "allowedCommonJsDependencies": ["chart.js", "js-sha256"], @@ -141,12 +141,9 @@ "outputHashing": "all" }, "development": { - "buildOptimizer": false, "optimization": false, - "vendorChunk": true, "extractLicenses": false, - "sourceMap": true, - "namedChunks": true + "sourceMap": true } }, "defaultConfiguration": "production" diff --git a/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts b/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts index 4a6287a7c0..44bf9f269e 100644 --- a/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts +++ b/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts @@ -1,12 +1,12 @@ import { AuthService, LocalizationPipe } from '@abp/ng.core'; import { Component, inject } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { ButtonComponent, CardBodyComponent, CardComponent } from '@abp/ng.theme.shared'; @Component({ selector: 'app-home', templateUrl: './home.component.html', - imports: [CommonModule, LocalizationPipe, CardComponent, CardBodyComponent, ButtonComponent], + imports: [NgTemplateOutlet, LocalizationPipe, CardComponent, CardBodyComponent, ButtonComponent], }) export class HomeComponent { protected readonly authService = inject(AuthService); 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-core/tsconfig.lib.json b/npm/ng-packs/packages/account-core/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/account-core/tsconfig.lib.json +++ b/npm/ng-packs/packages/account-core/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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..5b00896acb 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, @@ -9,7 +9,7 @@ import { import { finalize } from 'rxjs/operators'; import { LocalizationPipe } from '@abp/ng.core'; import { ButtonComponent } from '@abp/ng.theme.shared'; -import { RouterModule } from '@angular/router'; +import { RouterLink } from '@angular/router'; import { NgxValidateCoreModule } from '@ngx-validate/core'; @Component({ @@ -17,23 +17,23 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; templateUrl: 'forgot-password.component.html', imports: [ ReactiveFormsModule, - RouterModule, + RouterLink, LocalizationPipe, ButtonComponent, NgxValidateCoreModule, ], }) 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/login/login.component.ts b/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts index 37a3e45442..9144b36ba6 100644 --- a/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts @@ -5,7 +5,7 @@ import { UntypedFormGroup, Validators, } from '@angular/forms'; -import { RouterModule } from '@angular/router'; +import { RouterLink } from '@angular/router'; import { throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs/operators'; import { @@ -26,7 +26,7 @@ const { maxLength, required } = Validators; templateUrl: './login.component.html', imports: [ ReactiveFormsModule, - RouterModule, + RouterLink, LocalizationPipe, ButtonComponent, NgxValidateCoreModule, 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..4e749446f3 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,10 +1,10 @@ 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'; +import { NgClass, AsyncPipe } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; import { LocalizationPipe, ReplaceableTemplateDirective } from '@abp/ng.core'; import { PersonalSettingsComponent } from '../personal-settings/personal-settings.component'; @@ -23,7 +23,8 @@ import { ChangePasswordComponent } from '../change-password/change-password.comp `, ], imports: [ - CommonModule, + NgClass, + AsyncPipe, ReactiveFormsModule, PersonalSettingsComponent, ChangePasswordComponent, @@ -33,6 +34,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 +47,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..a826315311 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, @@ -18,7 +18,7 @@ import { catchError, finalize, switchMap } from 'rxjs/operators'; import { eAccountComponents } from '../../enums/components'; import { getRedirectUrl } from '../../utils/auth-utils'; import { NgxValidateCoreModule } from '@ngx-validate/core'; -import { RouterModule } from '@angular/router'; +import { RouterLink } from '@angular/router'; const { maxLength, required, email } = Validators; @@ -27,7 +27,7 @@ const { maxLength, required, email } = Validators; templateUrl: './register.component.html', imports: [ ReactiveFormsModule, - RouterModule, + RouterLink, NgxValidateCoreModule, LocalizationPipe, ButtonComponent, @@ -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..f4567c48bb 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,13 +1,13 @@ 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, UntypedFormGroup, Validators, } from '@angular/forms'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { comparePasswords, NgxValidateCoreModule, Validation } from '@ngx-validate/core'; import { finalize } from 'rxjs/operators'; import { LocalizationPipe } from '@abp/ng.core'; @@ -19,13 +19,19 @@ const PASSWORD_FIELDS = ['password', 'confirmPassword']; templateUrl: './reset-password.component.html', imports: [ ReactiveFormsModule, - RouterModule, + RouterLink, NgxValidateCoreModule, LocalizationPipe, ButtonComponent, ], }) 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/account/tsconfig.lib.json b/npm/ng-packs/packages/account/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/account/tsconfig.lib.json +++ b/npm/ng-packs/packages/account/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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/date-time-picker/extensible-date-time-picker.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts index 9f4455b1c2..907118283e 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts @@ -8,7 +8,6 @@ import { SkipSelf, ViewChild, } from '@angular/core'; -import { CommonModule } from '@angular/common'; import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { NgbDateAdapter, @@ -27,7 +26,6 @@ import { selfFactory } from '../../utils/factory.util'; @Component({ exportAs: 'abpExtensibleDateTimePicker', imports: [ - CommonModule, ReactiveFormsModule, NgbDatepickerModule, NgbTimepickerModule, diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts index 401bd15c3d..b1ba5c3fe6 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -1,7 +1,7 @@ import { EXTENSIONS_FORM_PROP, EXTENSIONS_FORM_PROP_DATA } from './../../tokens/extensions.token'; import { ABP, - LocalizationModule, + LocalizationPipe, PermissionDirective, ShowPasswordDirective, TrackByService, @@ -47,7 +47,7 @@ import { eExtensibleComponents } from '../../enums/components'; import { ExtensibleDateTimePickerComponent } from '../date-time-picker/extensible-date-time-picker.component'; import { NgxValidateCoreModule } from '@ngx-validate/core'; import { ExtensibleFormPropService } from '../../services/extensible-form-prop.service'; -import { CommonModule } from '@angular/common'; +import { AsyncPipe, NgClass, NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { ExtensibleFormMultiselectComponent } from '../multi-select/extensible-form-multiselect.component'; @@ -66,8 +66,11 @@ import { ExtensibleFormMultiselectComponent } from '../multi-select/extensible-f NgbTypeaheadModule, ShowPasswordDirective, PermissionDirective, - LocalizationModule, - CommonModule, + LocalizationPipe, + AsyncPipe, + NgClass, + NgComponentOutlet, + NgTemplateOutlet, FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts index 33f6988811..9d81502291 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts @@ -17,14 +17,14 @@ import { ExtensionsService } from '../../services/extensions.service'; import { EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token'; import { selfFactory } from '../../utils/factory.util'; import { ExtensibleFormPropComponent } from './extensible-form-prop.component'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgTemplateOutlet } from '@angular/common'; import { PropDataDirective } from '../../directives/prop-data.directive'; @Component({ exportAs: 'abpExtensibleForm', selector: 'abp-extensible-form', templateUrl: './extensible-form.component.html', - imports: [CommonModule, PropDataDirective, ReactiveFormsModule, ExtensibleFormPropComponent], + imports: [NgClass, NgTemplateOutlet, PropDataDirective, ReactiveFormsModule, ExtensibleFormPropComponent], changeDetection: ChangeDetectionStrategy.OnPush, viewProviders: [ { 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/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index bcdfffdfa4..0705f4bae6 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -25,7 +25,7 @@ import { ABP, ConfigStateService, ListService, - LocalizationModule, + LocalizationPipe, PermissionDirective, PermissionService, TimezoneService, @@ -63,7 +63,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150; NgxDatatableDefaultDirective, NgxDatatableListDirective, PermissionDirective, - LocalizationModule, + LocalizationPipe, UtcToLocalPipe, AsyncPipe, NgTemplateOutlet, 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..48bea482ca 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,15 +1,14 @@ -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'; import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component'; import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; -import { LocalizationModule, PermissionDirective } from '@abp/ng.core'; +import { LocalizationPipe, PermissionDirective } from '@abp/ng.core'; import { EllipsisDirective } from '@abp/ng.theme.shared'; import { NgClass, NgTemplateOutlet } from '@angular/common'; @@ -20,7 +19,7 @@ import { NgClass, NgTemplateOutlet } from '@angular/common'; EllipsisDirective, PermissionDirective, NgClass, - LocalizationModule, + LocalizationPipe, NgTemplateOutlet, NgbTooltipModule, ], @@ -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/multi-select/extensible-form-multiselect.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts index 96860b31b4..900800ee59 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts @@ -1,6 +1,5 @@ import { Component, ChangeDetectionStrategy, forwardRef, input } from '@angular/core'; -import { NG_VALUE_ACCESSOR, ControlValueAccessor, ReactiveFormsModule } from '@angular/forms'; -import { CommonModule } from '@angular/common'; +import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; import { ABP, LocalizationPipe } from '@abp/ng.core'; import { FormProp } from '../../models/form-props'; import { NgxValidateCoreModule } from '@ngx-validate/core'; @@ -37,7 +36,7 @@ const EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR = { `, providers: [EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR], - imports: [LocalizationPipe, CommonModule, ReactiveFormsModule, NgxValidateCoreModule], + imports: [LocalizationPipe, NgxValidateCoreModule], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExtensibleFormMultiselectComponent implements ControlValueAccessor { 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..9dadb7497a 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, @@ -9,7 +9,7 @@ import { import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token'; import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component'; import { CreateInjectorPipe } from '../../pipes/create-injector.pipe'; -import { LocalizationModule, PermissionDirective } from '@abp/ng.core'; +import { LocalizationPipe, PermissionDirective } from '@abp/ng.core'; import { NgClass, NgComponentOutlet } from '@angular/common'; @Component({ @@ -18,7 +18,7 @@ import { NgClass, NgComponentOutlet } from '@angular/common'; imports: [ CreateInjectorPipe, PermissionDirective, - LocalizationModule, + LocalizationPipe, NgClass, NgComponentOutlet, ], @@ -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/components/tree.component.ts b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts index 334ff11414..316200704b 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts +++ b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts @@ -28,7 +28,7 @@ import { of } from 'rxjs'; import { DISABLE_TREE_STYLE_LOADING_TOKEN } from '../disable-tree-style-loading.token'; import { TreeNodeTemplateDirective } from '../templates/tree-node-template.directive'; import { ExpandedIconTemplateDirective } from '../templates/expanded-icon-template.directive'; -import { CommonModule } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { NzNoAnimationDirective } from 'ng-zorro-antd/core/no-animation'; export type DropEvent = NzFormatEmitEvent & { pos: number }; @@ -41,7 +41,7 @@ export type DropEvent = NzFormatEmitEvent & { pos: number }; providers: [SubscriptionService], changeDetection: ChangeDetectionStrategy.OnPush, imports: [ - CommonModule, + NgTemplateOutlet, NzTreeComponent, NgbDropdown, NgbDropdownMenu, 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/components/tsconfig.lib.json b/npm/ng-packs/packages/components/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/components/tsconfig.lib.json +++ b/npm/ng-packs/packages/components/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], diff --git a/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts b/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts index 50473e4df1..020f750555 100644 --- a/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts +++ b/npm/ng-packs/packages/core/src/lib/abstracts/auth.guard.ts @@ -21,3 +21,9 @@ export const authGuard: CanActivateFn = () => { console.error('You should add @abp/ng-oauth packages or create your own auth packages.'); return false; }; + + +export const asyncAuthGuard: CanActivateFn = () => { + console.error('You should add @abp/ng-oauth packages or create your own auth packages.'); + return false; +}; \ No newline at end of file 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..6b9c945be3 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'; @@ -12,7 +12,7 @@ import { findRoute, getRoutePath } from '../utils/route-utils'; import { TreeNode } from '../utils/tree-utils'; import { DYNAMIC_LAYOUTS_TOKEN } from '../tokens/dynamic-layout.token'; import { EnvironmentService } from '../services'; -import { CommonModule } from '@angular/common'; +import { NgComponentOutlet } from '@angular/common'; @Component({ selector: 'abp-dynamic-layout', @@ -22,7 +22,7 @@ import { CommonModule } from '@angular/common'; } `, providers: [SubscriptionService], - imports: [CommonModule], + imports: [NgComponentOutlet], }) export class DynamicLayoutComponent implements OnInit { layout?: Type; @@ -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..6d6cc59191 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,10 +1,10 @@ -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'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { SubscriptionService } from '../services/subscription.service'; -import { CommonModule } from '@angular/common'; +import { NgComponentOutlet } from '@angular/common'; @Component({ selector: 'abp-replaceable-route-container', @@ -12,21 +12,19 @@ import { CommonModule } from '@angular/common'; `, providers: [SubscriptionService], - imports: [CommonModule], + imports: [NgComponentOutlet], }) 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/components/router-outlet.component.ts b/npm/ng-packs/packages/core/src/lib/components/router-outlet.component.ts index 73af8037c2..4a0dc6c099 100644 --- a/npm/ng-packs/packages/core/src/lib/components/router-outlet.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/router-outlet.component.ts @@ -1,9 +1,9 @@ import { Component } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import { RouterOutlet } from '@angular/router'; @Component({ selector: 'abp-router-outlet', template: ` `, - imports: [RouterModule], + imports: [RouterOutlet], }) export class RouterOutletComponent {} 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-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts index 0e17b37b04..9722ab7660 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts @@ -7,7 +7,6 @@ import { Router, RouterEvent, Event, - RouterState, } from '@angular/router'; import { Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; 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/src/lib/validators/unique-character.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/unique-character.validator.ts index 509dc66c7f..150a6f1632 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/unique-character.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/unique-character.validator.ts @@ -1,4 +1,4 @@ -import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; +import { AbstractControl, ValidatorFn } from '@angular/forms'; import { isNullOrEmpty } from '../utils'; export interface UniqueCharacterError { 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/core/tsconfig.lib.json b/npm/ng-packs/packages/core/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/core/tsconfig.lib.json +++ b/npm/ng-packs/packages/core/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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/feature-management/src/lib/components/feature-management/feature-management.component.ts b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts index bfadbf65fb..0355ef2bae 100644 --- a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts +++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; -import { CommonModule, NgTemplateOutlet } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { ConfigStateService, LocalizationPipe, TrackByService } from '@abp/ng.core'; import { @@ -35,14 +35,13 @@ const DEFAULT_PROVIDER_NAME = 'D'; templateUrl: './feature-management.component.html', exportAs: 'abpFeatureManagement', imports: [ - CommonModule, + NgTemplateOutlet, ButtonComponent, ModalComponent, LocalizationPipe, FormsModule, NgbNavModule, FreeTextInputDirective, - NgTemplateOutlet, ModalCloseDirective, ], }) diff --git a/npm/ng-packs/packages/feature-management/tsconfig.lib.json b/npm/ng-packs/packages/feature-management/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/feature-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/feature-management/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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/identity/tsconfig.lib.json b/npm/ng-packs/packages/identity/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/identity/tsconfig.lib.json +++ b/npm/ng-packs/packages/identity/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], diff --git a/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts b/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts index 6f751804cd..1311a8d7a8 100644 --- a/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts +++ b/npm/ng-packs/packages/oauth/src/lib/guards/oauth.guard.ts @@ -6,10 +6,10 @@ import { CanActivateFn, } from '@angular/router'; -import { Observable } from 'rxjs'; +import { Observable, timer, filter, take, map, firstValueFrom, timeout, catchError, of } from 'rxjs'; import { OAuthService } from 'angular-oauth2-oidc'; -import { AuthService, IAbpGuard } from '@abp/ng.core'; +import { AuthService, IAbpGuard, EnvironmentService } from '@abp/ng.core'; /** * @deprecated Use `abpOAuthGuard` *function* instead. @@ -53,3 +53,36 @@ export const abpOAuthGuard: CanActivateFn = ( authService.navigateToLogin(params); return false; }; + +export const asyncAbpOAuthGuard: CanActivateFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, +) => { + const oAuthService = inject(OAuthService); + const authService = inject(AuthService); + const environmentService = inject(EnvironmentService); + + const { oAuthConfig } = environmentService.getEnvironment(); + + if (oAuthConfig?.responseType === 'code') { + return firstValueFrom( + timer(0, 100).pipe( + map(() => oAuthService.hasValidAccessToken()), + filter(Boolean), + take(1), + timeout(3000), + catchError(() => { + authService.navigateToLogin({ returnUrl: state.url }); + return of(false); + }) + ) + ); + } + + if (oAuthService.hasValidAccessToken()) { + return true; + } + + authService.navigateToLogin({ returnUrl: state.url }); + return false; +}; 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/providers/oauth-module-config.provider.ts b/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts index 92ecc86012..534882dd13 100644 --- a/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts +++ b/npm/ng-packs/packages/oauth/src/lib/providers/oauth-module-config.provider.ts @@ -2,6 +2,7 @@ import { AuthService, AuthGuard, authGuard, + asyncAuthGuard, ApiInterceptor, PIPE_TO_LOGIN_FN_KEY, CHECK_AUTHENTICATION_STATE_FN_KEY, @@ -11,7 +12,7 @@ import { import { Provider, makeEnvironmentProviders, inject, provideAppInitializer } from '@angular/core'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc'; -import { AbpOAuthGuard, abpOAuthGuard } from '../guards'; +import { AbpOAuthGuard, abpOAuthGuard, asyncAbpOAuthGuard, } from '../guards'; import { OAuthConfigurationHandler } from '../handlers'; import { OAuthApiInterceptor } from '../interceptors'; import { AbpOAuthService, OAuthErrorFilterService } from '../services'; @@ -31,6 +32,10 @@ export function provideAbpOAuth() { { provide: authGuard, useValue: abpOAuthGuard, + }, + { + provide: asyncAuthGuard, + useValue: asyncAbpOAuthGuard, }, { provide: ApiInterceptor, 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/oauth/tsconfig.lib.json b/npm/ng-packs/packages/oauth/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/oauth/tsconfig.lib.json +++ b/npm/ng-packs/packages/oauth/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index 671e78893f..dd0492a411 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -30,7 +30,7 @@ import { import { concat, of } from 'rxjs'; import { finalize, switchMap, take, tap } from 'rxjs/operators'; import { PermissionManagement } from '../models'; -import { CommonModule } from '@angular/common'; +import { NgStyle } from '@angular/common'; import { FormsModule } from '@angular/forms'; type PermissionWithStyle = PermissionGrantInfoDto & { @@ -94,7 +94,7 @@ type PermissionWithGroupName = PermissionGrantInfoDto & { ], imports: [ FormsModule, - CommonModule, + NgStyle, ModalComponent, LocalizationPipe, ButtonComponent, diff --git a/npm/ng-packs/packages/permission-management/tsconfig.lib.json b/npm/ng-packs/packages/permission-management/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/permission-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/permission-management/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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/commands/create-lib/files-package-standalone/__libraryName@kebab__/tsconfig.lib.json.template b/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package-standalone/__libraryName@kebab__/tsconfig.lib.json.template index 5b574d313d..7b5ac72780 100644 --- a/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package-standalone/__libraryName@kebab__/tsconfig.lib.json.template +++ b/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package-standalone/__libraryName@kebab__/tsconfig.lib.json.template @@ -10,7 +10,7 @@ "types": [], "lib": [ "dom", - "es2018" + "es2020" ] }, "exclude": [ diff --git a/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.json.template b/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.json.template index 5b574d313d..7b5ac72780 100644 --- a/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.json.template +++ b/npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.json.template @@ -10,7 +10,7 @@ "types": [], "lib": [ "dom", - "es2018" + "es2020" ] }, "exclude": [ 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/schematics/tsconfig.json b/npm/ng-packs/packages/schematics/tsconfig.json index 591691b578..dcccc6bde1 100644 --- a/npm/ng-packs/packages/schematics/tsconfig.json +++ b/npm/ng-packs/packages/schematics/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "baseUrl": "tsconfig", - "lib": ["es2019", "dom"], + "lib": ["es2020", "dom"], "declaration": true, "module": "commonjs", "moduleResolution": "node", 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/setting-management/src/lib/components/setting-management.component.ts b/npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts index 7b26a647ce..1cec644ed6 100644 --- a/npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts +++ b/npm/ng-packs/packages/setting-management/src/lib/components/setting-management.component.ts @@ -2,13 +2,13 @@ import { ABP, ForDirective, LocalizationPipe, PermissionDirective } from '@abp/n import { SettingTabsService } from '@abp/ng.setting-management/config'; import { Component, inject, OnDestroy, OnInit, TrackByFunction } from '@angular/core'; import { Subscription } from 'rxjs'; -import { CommonModule } from '@angular/common'; +import { NgComponentOutlet } from '@angular/common'; import { PageComponent } from '@abp/ng.components/page'; @Component({ selector: 'abp-setting-management', templateUrl: './setting-management.component.html', - imports: [CommonModule, PageComponent, LocalizationPipe, PermissionDirective, ForDirective], + imports: [NgComponentOutlet, PageComponent, LocalizationPipe, PermissionDirective, ForDirective], }) export class SettingManagementComponent implements OnDestroy, OnInit { private settingTabsService = inject(SettingTabsService); diff --git a/npm/ng-packs/packages/setting-management/tsconfig.lib.json b/npm/ng-packs/packages/setting-management/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/setting-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/setting-management/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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/tenant-management/tsconfig.lib.json b/npm/ng-packs/packages/tenant-management/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/tenant-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/tenant-management/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], 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..3600c615c9 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,13 +1,13 @@ -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'; +import { NgTemplateOutlet } from '@angular/common'; import { LogoComponent } from '../logo/logo.component'; import { RoutesComponent } from '../routes/routes.component'; import { NavItemsComponent } from '../nav-items/nav-items.component'; import { AuthWrapperComponent } from './auth-wrapper/auth-wrapper.component'; import { PageAlertContainerComponent } from '../page-alert-container/page-alert-container.component'; -import { RouterModule } from '@angular/router'; +import { RouterOutlet } from '@angular/router'; import { collapseWithMargin } from '@abp/ng.theme.shared'; @Component({ @@ -16,24 +16,24 @@ import { collapseWithMargin } from '@abp/ng.theme.shared'; animations: [collapseWithMargin], providers: [LayoutService, SubscriptionService], imports: [ - CommonModule, + NgTemplateOutlet, LogoComponent, RoutesComponent, NavItemsComponent, AuthWrapperComponent, PageAlertContainerComponent, ReplaceableTemplateDirective, - RouterModule, + RouterOutlet, ], }) 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..e87c55dd04 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,6 +1,6 @@ import { AuthWrapperService } from '@abp/ng.account.core'; -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; import { LocalizationPipe, ReplaceableTemplateDirective } from '@abp/ng.core'; import { TenantBoxComponent } from '../tenant-box/tenant-box.component'; @@ -8,8 +8,8 @@ import { TenantBoxComponent } from '../tenant-box/tenant-box.component'; selector: 'abp-auth-wrapper', templateUrl: './auth-wrapper.component.html', providers: [AuthWrapperService], - imports: [CommonModule, TenantBoxComponent, ReplaceableTemplateDirective, LocalizationPipe], + imports: [AsyncPipe, TenantBoxComponent, ReplaceableTemplateDirective, LocalizationPipe], }) export class AuthWrapperComponent { - constructor(public service: AuthWrapperService) {} -} + service = inject(AuthWrapperService); +} \ No newline at end of file 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..c9897fed59 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,6 +1,6 @@ import { TenantBoxService } from '@abp/ng.account.core'; -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; import { LocalizationPipe } from '@abp/ng.core'; import { ButtonComponent, ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared'; import { FormsModule } from '@angular/forms'; @@ -10,7 +10,7 @@ import { FormsModule } from '@angular/forms'; templateUrl: './tenant-box.component.html', providers: [TenantBoxService], imports: [ - CommonModule, + AsyncPipe, FormsModule, ModalComponent, LocalizationPipe, @@ -19,5 +19,5 @@ import { FormsModule } from '@angular/forms'; ], }) export class TenantBoxComponent { - constructor(public service: TenantBoxService) {} + service = inject(TenantBoxService); } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts index 2cfeb978b2..0cebc476a5 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.ts @@ -2,8 +2,8 @@ import { eLayoutType, ReplaceableTemplateDirective, SubscriptionService } from ' import { collapseWithMargin, slideFromBottom } from '@abp/ng.theme.shared'; import { AfterViewInit, Component, inject } from '@angular/core'; import { LayoutService } from '../../services/layout.service'; -import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; +import { NgTemplateOutlet } from '@angular/common'; +import { RouterOutlet } from '@angular/router'; import { LogoComponent } from '../logo/logo.component'; import { PageAlertContainerComponent } from '../page-alert-container/page-alert-container.component'; import { RoutesComponent } from '../routes/routes.component'; @@ -15,13 +15,13 @@ import { NavItemsComponent } from '../nav-items/nav-items.component'; animations: [slideFromBottom, collapseWithMargin], providers: [LayoutService, SubscriptionService], imports: [ - CommonModule, + NgTemplateOutlet, LogoComponent, PageAlertContainerComponent, RoutesComponent, NavItemsComponent, ReplaceableTemplateDirective, - RouterModule, + RouterOutlet, ], }) export class ApplicationLayoutComponent implements AfterViewInit { diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/empty-layout/empty-layout.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/empty-layout/empty-layout.component.ts index 16cd0951e5..2153ecf2c5 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/empty-layout/empty-layout.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/empty-layout/empty-layout.component.ts @@ -1,11 +1,11 @@ import { Component } from '@angular/core'; import { eLayoutType } from '@abp/ng.core'; -import { RouterModule } from '@angular/router'; +import { RouterOutlet } from '@angular/router'; @Component({ selector: 'abp-layout-empty', template: ` `, - imports: [RouterModule], + imports: [RouterOutlet], }) export class EmptyLayoutComponent { static type = eLayoutType.empty; 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..0bc2266e0c 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,6 +1,6 @@ import { ApplicationInfo, EnvironmentService } from '@abp/ng.core'; -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; +import { Component, inject } from '@angular/core'; @Component({ selector: 'abp-logo', @@ -13,12 +13,12 @@ import { CommonModule } from '@angular/common'; } `, - imports: [CommonModule], + imports: [RouterLink], }) 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..cd6f087600 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,16 +9,17 @@ 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 { NgComponentOutlet, AsyncPipe } from '@angular/common'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'abp-current-user', templateUrl: './current-user.component.html', imports: [ - CommonModule, + NgComponentOutlet, + AsyncPipe, NgbDropdownModule, AbpVisibleDirective, PermissionDirective, @@ -27,6 +28,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 +43,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..eaf645c4a0 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,8 +1,8 @@ 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'; +import { AsyncPipe } from '@angular/common'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @Component({ @@ -39,9 +39,12 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; } `, - imports: [CommonModule, NgbDropdownModule], + imports: [AsyncPipe, 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..623c586aa7 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,15 +1,15 @@ import { AbpVisibleDirective, NavItem, NavItemsService } from '@abp/ng.theme.shared'; -import { Component, TrackByFunction } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { Component, TrackByFunction, inject } from '@angular/core'; +import { NgComponentOutlet, AsyncPipe } from '@angular/common'; import { PermissionDirective, ToInjectorPipe } from '@abp/ng.core'; @Component({ selector: 'abp-nav-items', templateUrl: 'nav-items.component.html', - imports: [CommonModule, AbpVisibleDirective, PermissionDirective, ToInjectorPipe], + imports: [NgComponentOutlet, AsyncPipe, 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..f40ba2cd91 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 { Component, ViewEncapsulation, inject } from '@angular/core'; import { PageAlertService } from '@abp/ng.theme.shared'; -import { CommonModule } from '@angular/common'; +import { NgClass, AsyncPipe } 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], + imports: [NgClass, AsyncPipe, LocalizationPipe, SafeHtmlPipe], }) export class PageAlertContainerComponent { - constructor(public service: PageAlertService) {} + service = inject(PageAlertService); } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts index 5d91c660da..5bc56ee765 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts @@ -16,17 +16,19 @@ import { TrackByFunction, ViewChildren, } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgTemplateOutlet, NgClass, AsyncPipe } from '@angular/common'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; -import { RouterModule } from '@angular/router'; +import { RouterLink } from '@angular/router'; import { EllipsisDirective } from '@abp/ng.theme.shared'; @Component({ selector: 'abp-routes', templateUrl: 'routes.component.html', imports: [ - CommonModule, - RouterModule, + NgTemplateOutlet, + NgClass, + AsyncPipe, + RouterLink, NgbDropdownModule, LazyLocalizationPipe, PermissionDirective, diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/validation-error/validation-error.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/validation-error/validation-error.component.ts index bfa9d6cab2..213ebb6306 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/validation-error/validation-error.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/validation-error/validation-error.component.ts @@ -1,4 +1,3 @@ -import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; import { Validation, ValidationErrorComponent as ErrorComponent } from '@ngx-validate/core'; import { LocalizationPipe } from '@abp/ng.core'; @@ -14,7 +13,7 @@ import { LocalizationPipe } from '@abp/ng.core'; `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - imports: [CommonModule, LocalizationPipe], + imports: [LocalizationPipe], }) export class ValidationErrorComponent extends ErrorComponent { get abpErrors(): (Validation.Error & { interpoliteParams?: string[] })[] { 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-basic/tsconfig.lib.json b/npm/ng-packs/packages/theme-basic/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/theme-basic/tsconfig.lib.json +++ b/npm/ng-packs/packages/theme-basic/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts index 0c68151d36..6e60a77669 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts @@ -1,12 +1,12 @@ import { Component, Input } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { CommonModule } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; +import { RouterLink } from '@angular/router'; import { ABP, LocalizationPipe } from '@abp/ng.core'; @Component({ selector: 'abp-breadcrumb-items', templateUrl: './breadcrumb-items.component.html', - imports: [CommonModule, RouterModule, LocalizationPipe], + imports: [ NgTemplateOutlet, RouterLink, LocalizationPipe], }) export class BreadcrumbItemsComponent { @Input() items: Partial[] = []; 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..3298b6f82c 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,15 +1,16 @@ /* 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 { NgClass } from '@angular/common'; import { ABP } from '@abp/ng.core'; @Component({ @@ -29,9 +30,11 @@ import { ABP } from '@abp/ng.core'; `, - imports: [CommonModule], + imports: [NgClass], }) 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/card/card-body.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts index 2376cef02e..35790054b5 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts @@ -1,12 +1,12 @@ import { Component, HostBinding, Input } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; @Component({ selector: 'abp-card-body', template: `
`, - imports: [CommonModule], + imports: [NgClass, NgStyle], }) export class CardBodyComponent { @HostBinding('class') componentClass = 'card-body'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts index 0e09e1fda6..38a798e146 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts @@ -1,5 +1,5 @@ import { Component, HostBinding, Input } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; @Component({ selector: 'abp-card-footer', @@ -9,7 +9,7 @@ import { CommonModule } from '@angular/common'; `, styles: [], - imports: [CommonModule], + imports: [NgClass, NgStyle], }) export class CardFooterComponent { @HostBinding('class') componentClass = 'card-footer'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts index 03e1fa8af4..4857913d77 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts @@ -1,5 +1,5 @@ import { Component, HostBinding, Input } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; @Component({ selector: 'abp-card-header', @@ -9,7 +9,7 @@ import { CommonModule } from '@angular/common'; `, styles: [], - imports: [CommonModule], + imports: [NgClass, NgStyle], }) export class CardHeaderComponent { @HostBinding('class') componentClass = 'card-header'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts index 5c2269b8aa..42973fdcb0 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts @@ -1,12 +1,12 @@ import { Component, Input } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; @Component({ selector: 'abp-card', template: `
`, - imports: [CommonModule], + imports: [NgClass, NgStyle], }) export class CardComponent { @Input() cardClass: string; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts index 9ee8cb1af5..8cadf828d1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core'; @Component({ @@ -31,7 +31,7 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core'; multi: true, }, ], - imports: [CommonModule, FormsModule, LocalizationPipe], + imports: [NgClass, NgStyle, FormsModule, LocalizationPipe], }) export class FormCheckboxComponent extends AbstractNgModelComponent { @Input() label?: string; 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..3ca2158c11 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,5 +1,5 @@ -import { Component, Inject } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgClass, AsyncPipe } from '@angular/common'; +import { Component, inject } from '@angular/core'; import { ReplaySubject } from 'rxjs'; import { Confirmation } from '../../models/confirmation'; import { CONFIRMATION_ICONS, ConfirmationIcons } from '../../tokens/confirmation-icons.token'; @@ -9,10 +9,11 @@ import { LocalizationPipe } from '@abp/ng.core'; selector: 'abp-confirmation', templateUrl: './confirmation.component.html', styleUrls: ['./confirmation.component.scss'], - imports: [CommonModule, LocalizationPipe], + imports: [NgClass, AsyncPipe, 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/form-input/form-input.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts index 73ad3306b1..2d10ab5709 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core'; @Component({ @@ -32,7 +32,7 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core'; multi: true, }, ], - imports: [CommonModule, LocalizationPipe, FormsModule], + imports: [NgClass, NgStyle, LocalizationPipe, FormsModule], }) export class FormInputComponent extends AbstractNgModelComponent { @Input() inputId!: string; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts index 04f5a9a5ab..9963868ed1 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/internet-connection-status/internet-connection-status.component.ts @@ -1,10 +1,10 @@ import { Component, inject } from '@angular/core'; -import { InternetConnectionService, LocalizationModule } from '@abp/ng.core'; +import { InternetConnectionService, LocalizationPipe } from '@abp/ng.core'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'abp-internet-status', - imports: [LocalizationModule, NgbTooltip], + imports: [LocalizationPipe, NgbTooltip], template: ` @if (!isOnline()) {
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..244b055322 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,6 +1,5 @@ -import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import { CommonModule } from '@angular/common'; +import { NgClass, NgStyle } from '@angular/common'; +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject } from '@angular/core'; import { combineLatest, Subscription, timer } from 'rxjs'; import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core'; @@ -21,9 +20,14 @@ import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng `, styleUrls: ['./loader-bar.component.scss'], providers: [SubscriptionService], - imports: [CommonModule], + imports: [NgClass, NgStyle], }) 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/components/modal/modal.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts index 3ac175ae6c..5e03aa5e75 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts @@ -12,7 +12,7 @@ import { output, viewChild, } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { SubscriptionService, uuid } from '@abp/ng.core'; import { NgbModal, NgbModalOptions, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; @@ -31,7 +31,7 @@ export type ModalSize = 'sm' | 'md' | 'lg' | 'xl'; templateUrl: './modal.component.html', styleUrls: ['./modal.component.scss'], providers: [SubscriptionService], - imports: [CommonModule], + imports: [NgTemplateOutlet], }) export class ModalComponent implements OnInit, OnDestroy, DismissableModal { protected readonly confirmationService = inject(ConfirmationService); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts index a6e670db0b..eeefb3ac01 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts @@ -1,6 +1,6 @@ import { Component, forwardRef, Input } from '@angular/core'; -import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; -import { CommonModule } from '@angular/common'; +import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NgClass } from '@angular/common'; import { AbstractNgModelComponent } from '@abp/ng.core'; import { NgxValidateCoreModule } from '@ngx-validate/core'; @@ -10,7 +10,7 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; */ @Component({ selector: 'abp-password', - imports: [CommonModule, FormsModule, ReactiveFormsModule, NgxValidateCoreModule], + imports: [NgClass, FormsModule, NgxValidateCoreModule], templateUrl: `./password.component.html`, providers: [ { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts index 8bb1a7126e..7834a87c05 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { NgClass } from '@angular/common'; import { Toaster } from '../../models/toaster'; import { LocalizationPipe } from '@abp/ng.core'; @@ -7,7 +7,7 @@ import { LocalizationPipe } from '@abp/ng.core'; selector: 'abp-toast', templateUrl: './toast.component.html', styleUrls: ['./toast.component.scss'], - imports: [CommonModule, LocalizationPipe], + imports: [NgClass, LocalizationPipe], }) export class ToastComponent implements OnInit { @Input() 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/ng-packs/packages/theme-shared/tsconfig.lib.json b/npm/ng-packs/packages/theme-shared/tsconfig.lib.json index 58b88ce56c..22d2695db8 100644 --- a/npm/ng-packs/packages/theme-shared/tsconfig.lib.json +++ b/npm/ng-packs/packages/theme-shared/tsconfig.lib.json @@ -7,7 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"], + "lib": ["dom", "es2020"], "useDefineForClassFields": false }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], diff --git a/npm/ng-packs/source-code-requirements/tsconfig.lib.json b/npm/ng-packs/source-code-requirements/tsconfig.lib.json index 884bdb3be7..df57fcc946 100644 --- a/npm/ng-packs/source-code-requirements/tsconfig.lib.json +++ b/npm/ng-packs/source-code-requirements/tsconfig.lib.json @@ -6,7 +6,7 @@ "declaration": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"] + "lib": ["dom", "es2020"] }, "angularCompilerOptions": { "enableIvy": true, diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index d24de94706..f863f18520 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -10,7 +10,8 @@ "importHelpers": true, "target": "es2020", "module": "esnext", - "lib": ["es2017", "dom"], + "lib": ["es2020", "dom"], + "esModuleInterop": true, "baseUrl": "./", "allowSyntheticDefaultImports": true, "paths": { 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/nupkg/common.ps1 b/nupkg/common.ps1 index 9dbce186ee..d6f3bab715 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -221,6 +221,7 @@ $projects = ( "framework/src/Volo.Abp.Ldap", "framework/src/Volo.Abp.Localization.Abstractions", "framework/src/Volo.Abp.MailKit", + "framework/src/Volo.Abp.Mapperly", "framework/src/Volo.Abp.Maui.Client", "framework/src/Volo.Abp.Localization", "framework/src/Volo.Abp.MemoryDb", diff --git a/templates/app-nolayers/angular/angular.json b/templates/app-nolayers/angular/angular.json index bfdb10c374..6ed0811fb5 100644 --- a/templates/app-nolayers/angular/angular.json +++ b/templates/app-nolayers/angular/angular.json @@ -19,12 +19,12 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/MyProjectName", "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", + "browser": "src/main.ts", + "polyfills": ["src/polyfills.ts"], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "allowedCommonJsDependencies": ["chart.js", "js-sha256"], @@ -137,12 +137,9 @@ "outputHashing": "all" }, "development": { - "buildOptimizer": false, "optimization": false, - "vendorChunk": true, "extractLicenses": false, - "sourceMap": true, - "namedChunks": true + "sourceMap": true } }, "defaultConfiguration": "production" @@ -168,8 +165,8 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", + "browser": "src/test.ts", + "polyfills": ["src/polyfills.ts"], "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", "inlineStyleLanguage": "scss", diff --git a/templates/app-nolayers/angular/src/app/home/home.component.ts b/templates/app-nolayers/angular/src/app/home/home.component.ts index 4fa3761541..3a5856c7c2 100644 --- a/templates/app-nolayers/angular/src/app/home/home.component.ts +++ b/templates/app-nolayers/angular/src/app/home/home.component.ts @@ -1,12 +1,12 @@ import {AuthService, LocalizationPipe} from '@abp/ng.core'; import { Component, inject } from '@angular/core'; -import {CommonModule} from "@angular/common"; +import {NgTemplateOutlet} from "@angular/common"; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], - imports: [CommonModule, LocalizationPipe], + imports: [NgTemplateOutlet, LocalizationPipe], }) export class HomeComponent { private authService = inject(AuthService); diff --git a/templates/app-nolayers/angular/tsconfig.json b/templates/app-nolayers/angular/tsconfig.json index 29df0877f5..eeb0e33782 100644 --- a/templates/app-nolayers/angular/tsconfig.json +++ b/templates/app-nolayers/angular/tsconfig.json @@ -11,10 +11,11 @@ "moduleResolution": "bundler", "importHelpers": true, "target": "ES2022", - "module": "es2020", + "module": "esnext", "skipLibCheck": true, + "esModuleInterop": true, "lib": [ - "es2018", + "es2020", "dom" ], "paths": { diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index a612f3322b..6560c390fa 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -18,7 +18,6 @@ - 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.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 04d3439850..98f5cdba90 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -18,7 +18,6 @@ - diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj index dd64f3032f..59edb352e6 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj @@ -72,7 +72,6 @@ - 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.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj index f378584d5c..ec9700e18b 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj @@ -73,7 +73,6 @@ - diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj index 2cfb6401e0..3e124600bb 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj @@ -68,7 +68,6 @@ - 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.Host/MyCompanyName.MyProjectName.Host.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj index f05ec35849..551d01521b 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj @@ -69,7 +69,6 @@ - diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj index ab1cad55cd..e48f2044fd 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj @@ -16,7 +16,6 @@ - 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-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj index c771ce0cf9..3d70cebc18 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj @@ -16,7 +16,6 @@ - diff --git a/templates/app/angular/angular.json b/templates/app/angular/angular.json index bfdb10c374..6ed0811fb5 100644 --- a/templates/app/angular/angular.json +++ b/templates/app/angular/angular.json @@ -19,12 +19,12 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/MyProjectName", "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", + "browser": "src/main.ts", + "polyfills": ["src/polyfills.ts"], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "allowedCommonJsDependencies": ["chart.js", "js-sha256"], @@ -137,12 +137,9 @@ "outputHashing": "all" }, "development": { - "buildOptimizer": false, "optimization": false, - "vendorChunk": true, "extractLicenses": false, - "sourceMap": true, - "namedChunks": true + "sourceMap": true } }, "defaultConfiguration": "production" @@ -168,8 +165,8 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", + "browser": "src/test.ts", + "polyfills": ["src/polyfills.ts"], "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", "inlineStyleLanguage": "scss", diff --git a/templates/app/angular/src/app/home/home.component.ts b/templates/app/angular/src/app/home/home.component.ts index cf42b0f606..420edd4724 100644 --- a/templates/app/angular/src/app/home/home.component.ts +++ b/templates/app/angular/src/app/home/home.component.ts @@ -1,12 +1,12 @@ import {AuthService, LocalizationPipe} from '@abp/ng.core'; import { Component, inject } from '@angular/core'; -import {CommonModule} from "@angular/common"; +import {NgTemplateOutlet} from "@angular/common"; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], - imports: [CommonModule, LocalizationPipe] + imports: [NgTemplateOutlet, LocalizationPipe] }) export class HomeComponent { private authService = inject(AuthService); diff --git a/templates/app/angular/tsconfig.json b/templates/app/angular/tsconfig.json index 29df0877f5..eeb0e33782 100644 --- a/templates/app/angular/tsconfig.json +++ b/templates/app/angular/tsconfig.json @@ -11,10 +11,11 @@ "moduleResolution": "bundler", "importHelpers": true, "target": "ES2022", - "module": "es2020", + "module": "esnext", "skipLibCheck": true, + "esModuleInterop": true, "lib": [ - "es2018", + "es2020", "dom" ], "paths": { 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/angular/angular.json b/templates/module/angular/angular.json index b6145d12f1..02fd552ffe 100644 --- a/templates/module/angular/angular.json +++ b/templates/module/angular/angular.json @@ -58,12 +58,12 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/dev-app", "index": "projects/dev-app/src/index.html", - "main": "projects/dev-app/src/main.ts", - "polyfills": "projects/dev-app/src/polyfills.ts", + "browser": "projects/dev-app/src/main.ts", + "polyfills": ["projects/dev-app/src/polyfills.ts"], "tsConfig": "projects/dev-app/tsconfig.app.json", "inlineStyleLanguage": "scss", "allowedCommonJsDependencies": ["chart.js", "js-sha256"], @@ -131,12 +131,9 @@ "outputHashing": "all" }, "development": { - "buildOptimizer": false, "optimization": false, - "vendorChunk": true, "extractLicenses": false, - "sourceMap": true, - "namedChunks": true + "sourceMap": true } }, "defaultConfiguration": "production" @@ -162,8 +159,8 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "main": "projects/dev-app/src/test.ts", - "polyfills": "projects/dev-app/src/polyfills.ts", + "browser": "projects/dev-app/src/test.ts", + "polyfills": ["projects/dev-app/src/polyfills.ts"], "tsConfig": "projects/dev-app/tsconfig.spec.json", "karmaConfig": "projects/dev-app/karma.conf.js", "inlineStyleLanguage": "scss", diff --git a/templates/module/angular/tsconfig.prod.json b/templates/module/angular/tsconfig.prod.json index bd2235775b..28b0a75f51 100644 --- a/templates/module/angular/tsconfig.prod.json +++ b/templates/module/angular/tsconfig.prod.json @@ -9,10 +9,11 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, - "target": "es2017", - "module": "es2020", + "target": "es2020", + "module": "esnext", + "esModuleInterop": true, "lib": [ - "es2018", + "es2020", "dom" ], }, 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.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index eb3d5c8d53..3ffec54e2c 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index d53b3d90bc..61534b55c8 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using IdentityModel; +using Duende.IdentityModel; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; 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 diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/ClientDemoService.cs b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/ClientDemoService.cs index 8290b2df36..95b0fd717d 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/ClientDemoService.cs +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/ClientDemoService.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using Microsoft.Extensions.Configuration; using MyCompanyName.MyProjectName.Samples; using Volo.Abp.DependencyInjection;