diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml
index 21189e3a30..029830c7c5 100644
--- a/.github/ISSUE_TEMPLATE/02_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml
@@ -1,6 +1,6 @@
name: 💡 Feature request
description: Suggest an idea for this project
-labels: [feature]
+labels: [feature-request]
body:
- type: checkboxes
attributes:
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..43e9cba5de
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,14 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 7.x.x | :white_check_mark: |
+| < 7.0.0 | :x: |
+
+## Reporting a Vulnerability
+
+Please don not share vulnerabilities publicly in GitHub or other platforms.
+You can report security issues by sending a email to `security@abp.io`
+Your report is immediately evaluated. We publish patch versions for critical vulnerabilities in a week at most.
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
index e5df7c7915..3766c15bc9 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
@@ -493,6 +493,44 @@
"QuotationTemplate.Tax/VAT:": "Tax / Vat (%{0}) :",
"QuotationTemplate.TotalDiscount:": "Total Discount :",
"QuotationTemplate.TOTALDUE:": "TOTAL DUE :",
- "QuotationTemplate.BankAccount": "Our bank account information can be found at {0}"
+ "QuotationTemplate.BankAccount": "Our bank account information can be found at {0}",
+ "Permission:Raffles": "Raffle",
+ "Permission:Draw": "Draw",
+ "Menu:Raffles": "Raffles",
+ "RaffleIsNotDrawable": "Raffle is not drawable",
+ "WinnerCountMustBeGreaterThanZero": "Winner count must be greater than zero",
+ "FullDescription": "Full Description",
+ "VisibilityStartDate": "Visibility Start Date",
+ "VisibilityEndDate": "Visibility End Date",
+ "RaffleDate": "Raffle Date",
+ "SubscriptionCode": "Subscription Code",
+ "GroupCode": "Group Code",
+ "MaxWinnerCount": "Max Winner Count",
+ "ReDraw": "Re-Draw",
+ "EditRaffle": "Edit Raffle",
+ "Raffles": "Raffles",
+ "CreateARaffle": "Create a raffle",
+ "Draw": "Draw",
+ "Enum:RaffleStatus:0": "Active",
+ "Enum:RaffleStatus:1": "Next",
+ "Enum:RaffleStatus:2": "Past",
+ "DrawDone": "Draw Done",
+ "HomePageShowType": "Home Page Show Type",
+ "None": "None",
+ "Card": "Card",
+ "Horizontal": "Horizontal",
+ "Winners": "Winners",
+ "StartDateMustBeLessThanEndDate": "Start date must be less than end date",
+ "VisibilityStartDateMustBeLessThanVisibilityEndDate": "Visibility start date must be less than visibility end date",
+ "StartDateMustBeGreaterThanVisibilityStartDate": "Start date must be greater than visibility start date",
+ "EndDateMustBeLessThanVisibilityEndDate": "End date must be less than visibility end date",
+ "DrawnDone": "Drawn Done",
+ "AddColor": "Add Color",
+ "Colors": "Colors",
+ "RemoveColor": "Remove Color",
+ "MaxColorCountWarning": "You can add up to {0} colors",
+ "MinColorCountWarning": "You must add at least {0} colors",
+ "RaffleDeletionConfirmationMessage": "Are you sure you want to delete this raffle?",
+ "CreateRaffle": "Create Raffle"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json
index 4a7e64125f..d3537d3742 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json
@@ -167,7 +167,7 @@
"ABPDiscordServer": "ABP سيرفر الدسكورد",
"ABPCommunityTalks": "برامج منتدى ABP الحوارية",
"ABPCommunityPosts": "منشورات منتدى ABP",
- "BuyAndGetMonths": "شراء 12 شهر، احصل على 14 شهرا!",
+ "BuyAndGetMonths": "شراء 12 شهر، احصل على 14 شهرا!",
"GetYourDeal": "احصل على صفقتك",
"BuyOrRenewLicense": "اشترِ أو جدد الرخصة الآن واحصل على شهرين إضافيين!",
"BuyOrRenewLicenseToGetExtra2Months": "اشترِ أو جدد الرخصة الآن واحصل على شهرين إضافيين! اسرع! ⏰ آخر يوم: {0}",
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
index 9ef432a8bd..63f78103d9 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
@@ -171,7 +171,7 @@
"ABPDiscordServer": "ABP Discord Server",
"ABPCommunityTalks": "ABP Community Talks",
"ABPCommunityPosts": "ABP Community Posts",
- "BuyAndGetMonths": "BUY 12 MONTHS, GET 14 MONTHS!",
+ "BuyAndGetMonths": "BUY 12 MONTHS, GET 14 MONTHS!",
"GetYourDeal": "Get Your Deal",
"BuyOrRenewLicense": "Buy or Renew License Now and Get 2 Extra Months!",
"BuyOrRenewLicenseToGetExtra2Months": "Buy or Renew License Now and Get 2 Extra Months! HURRY UP! ⏰ Last Day: {0}",
@@ -187,7 +187,7 @@
"GiveAwayForNewPurchases": "Application Development Classroom Training will be given away for the new purchases!",
"BlackFriday": "BLACKFRIDAY",
"ValidForExistingCustomers": "Also valid for the existing customers!",
- "CampaignBetweenDates": "From {0} to {1}",
+ "CampaignBetweenDates": "From {0} To {1}",
"SaveUpTo": "SAVE UP TO${0}K",
"ImplementingDDD": "Implementing Domain Driven Design",
"ExploreTheEBook": "Explore the E-Book",
@@ -220,6 +220,7 @@
"NoContent": "No content",
"More": "More",
"WhyABPIOPlatform": "Why ABP.IO Platform?",
- "AbpStudio": "ABP Studio"
+ "AbpStudio": "ABP Studio",
+ "ExtraMonths": "{0}EXTRA MONTHS"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json
index 16437480ef..80f351043f 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json
@@ -170,7 +170,7 @@
"ABPDiscordServer": "ABP Discord-palvelin",
"ABPCommunityTalks": "ABP Community Talks",
"ABPCommunityPosts": "ABP-yhteisön viestit",
- "BuyAndGetMonths": "OSTA 12 KUUKAUTA, SAAT 14 KUUKAUTA!",
+ "BuyAndGetMonths": "OSTA 12 KUUKAUTA, SAAT 14 KUUKAUTA!",
"GetYourDeal": "Hanki tarjouksesi",
"BuyOrRenewLicense": "Osta tai uusi lisenssi nyt ja saat 2 lisäkuukautta!",
"BuyOrRenewLicenseToGetExtra2Months": "Osta tai uusi lisenssi nyt ja saat 2 lisäkuukautta! KIIREHDI! ⏰ Viimeinen päivä: {0}",
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json
index b9ccddecf4..d785f418aa 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json
@@ -168,7 +168,7 @@
"ABPDiscordServer": "ABP Discord szerver",
"ABPCommunityTalks": "ABP közösségi beszélgetések",
"ABPCommunityPosts": "ABP közösségi bejegyzések",
- "BuyAndGetMonths": "VÁSÁROLJON 12 HÓNAPOT, 14 HÓNAPOT KAP!",
+ "BuyAndGetMonths": "VÁSÁROLJON 12 HÓNAPOT, 14 HÓNAPOT KAP!",
"GetYourDeal": "Szerezze meg az ajánlatát",
"BuyOrRenewLicense": "Vásároljon vagy újítson meg licencet most, és 2 további hónapot kap!",
"BuyOrRenewLicenseToGetExtra2Months": "Vásároljon vagy újítson meg licencet most, és 2 további hónapot kap! SIESS! ⏰ Utolsó nap: {0}",
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json
index d1a0db7176..545a909abf 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json
@@ -170,7 +170,7 @@
"ABPDiscordServer": "ABP Discord 服务器",
"ABPCommunityTalks": "ABP社区讲话",
"ABPCommunityPosts": "ABP社区文章",
- "BuyAndGetMonths": "购买 12 个月,获得 14 个月!",
+ "BuyAndGetMonths": "购买 12 个月,获得 14 个月!",
"GetYourDeal": "得到你的交易",
"BuyOrRenewLicense": "立即购买或续订许可证并额外获得 2 个月!",
"BuyOrRenewLicenseToGetExtra2Months": "立即购买或续订 ABP 商业许可证(适用于所有版本)并额外获得 2 个月!",
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
index 1b8c972d5c..4ec259664c 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
@@ -96,7 +96,7 @@
"See All Modules": "SeeAllModules",
"ABPSuite": "ABP Suite",
"AbpSuiteShortDescription": "ABP Suite is a complementary tool to ABP Commercial.",
- "AbpSuiteExplanation": "It allows you to build web pages in a matter of minutes. It's a .NET Core Global tool that can be installed from the command line. It can create a new ABP solution, and generate CRUD pages from the database to the front-end.",
+ "AbpSuiteExplanation": "It allows you to build web pages in a matter of minutes. It's a .NET Core Global tool that can be installed from the command line. It can create a new ABP solution and generate CRUD pages from the database to the front-end.",
"Details": "Details",
"LeptonTheme": "Lepton Theme",
"ProfessionalModernUIThemes": "Professional, modern UI themes",
@@ -177,17 +177,17 @@
"StartDevelopWithTutorials": "The downloaded solution is well architected and documented. You can start developing your own business code based on it following the tutorials.",
"TryTheCommercialDemo": "You can try the Live Demo to see a sample application created using the ABP Commercial startup template.",
"HowManyProducts": "How many different products/solutions can I build using the ABP Commercial?",
- "HowManyProductsExplanation": "You can create as many projects as you want during your active license period, there is no limit! After your license expires, you cannot create new projects, but you can continue to develop the projects you have downloaded and deploy them to an unlimited count of servers.",
+ "HowManyProductsExplanation": "You can create as many projects as you want during your active license period; there is no limit! After your license expires, you cannot create new projects, but you can continue to develop the projects you have downloaded and deploy them to an unlimited count of servers.",
"HowManyDevelopers": "How many developers can work on the ABP Commercial?",
"HowManyDevelopersExplanation": "ABP Commercial licenses are per developer. Different license types have different developer limits. However, you can add more developers to any license type whenever you need. Check out the Plans & Pricing page for license types, developer limits and additional developer costs.",
"ChangingLicenseType": "Can I upgrade my license type later?",
"ChangingLicenseTypeExplanation": "You can upgrade to a higher license by paying the difference within your active license period. When you upgrade to a higher license plan, you get the benefits of the new plan, but the license upgrade does not change the license expiry date. Besides, you can also add new developer seats to your existing license, check out the \"How many developers can work on the ABP Commercial?\" FAQ.",
"LicenseExtendUpgradeDiff": "What is the difference between license extend and upgrade?",
- "LicenseExtendUpgradeDiffExplanation": "Extending: By extending/renewing your license, you will continue to get premium support and get major or minor updates for the modules and themes. Besides, you will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development. When you extend your license, 1 year is added to your license expiry date.
Upgrading: By upgrading your license, you will promote to a higher license plan which will allow you to get additional benefits. Check out the license comparison table to see the differences between the license plans. On the other hand, when you upgrade, your license expiry date will not change! To extend your license end date, you need to extend your license.",
+ "LicenseExtendUpgradeDiffExplanation": "Extending: By extending/renewing your license, you will continue to get premium support and get major or minor updates for the modules and themes. Besides, you will be able to continue creating new projects. And you will still be able to use ABP Suite, which speeds up your development. When you extend your license, 1 year is added to your license expiry date. Upgrading: By upgrading your license, you will be promoted to a higher license plan, which will allow you to get additional benefits. Check out the license comparison table to see the differences between the license plans. On the other hand, when you upgrade, your license expiry date will not change! To extend your license end date, you need to extend your license.",
"LicenseRenewalCost": "What is the license renewal cost after 1 year?",
"LicenseRenewalCostExplanation": "The renewal (extend) price of the standard Team License is ${0}, standard Business License is ${1} and standard Enterprise License is ${2}. If you are already a customer, log into your account to review the current renewal pricing.",
"HowDoIRenewMyLicense": "How do I renew my license?",
- "HowDoIRenewMyLicenseExplanation": "You can renew your license by navigating to the organization management page. In order to take advantage of our discounted Early Renewal rates, ensure you renew before your license expires. Don't worry about not knowing when your Early Renewal opportunity closes, you'll however receive 3 reminder e-mails before your subscription expires. We'll send them 30 days, 7 days and 1 day before expiration.",
+ "HowDoIRenewMyLicenseExplanation": "You can renew your license by navigating to the organization management page. In order to take advantage of our discounted Early Renewal rates, ensure you renew before your license expires. Don't worry about not knowing when your Early Renewal opportunity closes; you'll receive 3 reminder e-mails before your subscription expires. We'll send them 30 days, 7 days and 1 day before expiration.",
"IsSourceCodeIncluded": "Does my license include the source code of the commercial modules and themes?",
"IsSourceCodeIncludedExplanation1": "Depends on the license type you've purchased:",
"IsSourceCodeIncludedExplanation2": "Team: Your solution uses the modules and themes as NuGet and NPM packages. It doesn't include their source code. This way, you can easily upgrade these modules and themes whenever a new version is available. However, you can not get the source code of these modules and themes.",
@@ -198,7 +198,7 @@
"WhatHappensWhenLicenseEnds": "What happens when my license period ends?",
"WhatHappensWhenLicenseEndsExplanation1": "The ABP Commercial license is a perpetual license. After your license expires, you can continue developing your project. And you are not obliged to renew your license. Your license comes with a one-year update and support plan out of the box. In order to continue to get new features, performance enhancements, bug fixes, support and continue using ABP Suite, you need to renew your license. When your license expires;",
"WhatHappensWhenLicenseEndsExplanation2": "You can not create new solutions using the ABP Commercial, but you can continue developing your existing applications forever.",
- "WhatHappensWhenLicenseEndsExplanation3": "You will be able to get updates for the modules and themes within your MINOR version (except RC or Preview versions). For example: if you are using v3.2.0 of a module, you can still get updates for v3.2.x (v3.2.1, v3.2.5... etc.) of that module. But you cannot get updates for the next major or minor version (like v3.3.0, v3.3.3, 4.x.x.. etc.). For example, when your license expired, the latest release was v4.4.3, and later, it published both 4.4.4 version and 4.5.0 version, you would be able to access the v4.4.X but you wouldn't be access the v4.5.X.",
+ "WhatHappensWhenLicenseEndsExplanation3": "You will be able to get updates for the modules and themes within your MINOR version (except RC or Preview versions). For example, if you are using v3.2.0 of a module, you can still get updates for v3.2.x (v3.2.1, v3.2.5... etc.) of that module. But you cannot get updates for the next major or minor version (like v3.3.0, v3.3.3, 4.x.x.. etc.). For example, when your license expired, the latest release was v4.4.3, and later, it published both 4.4.4 version and 4.5.0 version, you would be able to access the v4.4.X but you wouldn't be access the v4.5.X.",
"WhatHappensWhenLicenseEndsExplanation4": "You can not install new modules and themes added to the ABP Commercial platform after your license ends.",
"WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.",
"WhatHappensWhenLicenseEndsExplanation6": "You can not get the premium support anymore.",
@@ -206,9 +206,9 @@
"discountForYears": "{0}% discount for {1} year(s)",
"WhatHappensWhenLicenseEndsExplanation8": "The ABP projects you generated are not stored on our servers. Therefore, it is your responsibility to keep the source code you download. When your license expires, there's no way to get your generated ABP project source code.",
"WhenShouldIRenewMyLicense": "When should I renew my license?",
- "WhenShouldIRenewMyLicenseExplanation": "If you renew your license within {3} days after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}. However, if you renew your license after {3} days since the expiry date of your license, the renewal price will be the same as the license purchase price and there will be no discount on your renewal.",
+ "WhenShouldIRenewMyLicenseExplanation": "If you renew your license within {3} days after your license expires, the following discounts will be applied: Team License {0}; Business License {1}; Enterprise License {2}. However, if you renew your license after {3} days since the expiry date of your license, the renewal price will be the same as the license purchase price, and there will be no discount on your renewal.",
"TrialPlan": "Do you have a trial plan?",
- "TrialPlanExplanation": "Yes, to start your free trial contact marketing@volosoft.com. We also offer a 30-day money-back guarantee for the Team license, no questions asked! You can request a full refund within the first 30 days of the license purchase. We provide a 60% refund within 30 days for Business and Enterprise licenses. This is because the Business and Enterprise licenses contain the full source-code of all the modules and themes.",
+ "TrialPlanExplanation": "Yes, to start your free trial, contact marketing@volosoft.com. We also offer a 30-day money-back guarantee for the Team license, no questions asked! You can request a full refund within the first 30 days of the license purchase. We provide a 60% refund within 30 days for Business and Enterprise licenses. This is because the Business and Enterprise licenses contain the full source-code of all the modules and themes.",
"DoYouAcceptBankWireTransfer": "Do you accept bank wire transfers?",
"DoYouAcceptBankWireTransferExplanation": "Yes, we accept bank wire transfers. After sending the license fee via bank transfer, send your receipt and requested license type to accounting@volosoft.com. Our international bank account information:",
"HowToUpgrade": "How to upgrade existing applications when a new version is available?",
@@ -224,20 +224,20 @@
"MicroserviceSupportExplanation2": "All the ABP Commercial modules are designed to support microservice deployment scenarios (with its own API and database) by following the Module Development Best Practices document.",
"MicroserviceSupportExplanation3": "We provide a sample Microservice Demo Solution that demonstrates a microservice architecture implementation to help you create your own solution.",
"MicroserviceSupportExplanation4": "So, the short answer is: \"Yes, it supports microservice architecture\".",
- "MicroserviceSupportExplanation5": "However, a microservice system is a solution and every solution will have different requirements, network topology, communication scenarios, authentication possibilities, database sharding/partitioning decisions, runtime configurations, 3rd party system integrations and many more.",
+ "MicroserviceSupportExplanation5": "However, a microservice system is a solution, and every solution will have different requirements, network topology, communication scenarios, authentication possibilities, database sharding/partitioning decisions, runtime configurations, 3rd party system integrations and many more.",
"MicroserviceSupportExplanation6": "The ABP Framework and ABP Commercial provide infrastructure for microservice scenarios, microservice compatible modules, samples and documentation to help you build your own solution. But don't expect to directly download your dream solution pre-built for you. You will need to understand it and bring specific parts together based on your requirements.",
"WhereCanIDownloadSourceCode": "Where can I download the source-code?",
"WhereCanIDownloadSourceCodeExplanation": "You can download the source code of all the ABP modules, Angular packages and themes via ABP Suite or ABP CLI. Check out How to download the source-code?",
"ComputerLimitation": "How many computers can a developer login when developing ABP?",
- "ComputerLimitationExplanation": "We specifically permit {0} computers per individual/licensed developer. Whenever there is a need for a developer to develop ABP Commercial products on a third machine, an e-mail should be sent to license@abp.io explaining the situation and we will then make the appropriate allocation in our system.",
+ "ComputerLimitationExplanation": "We specifically permit {0} computers per individual/licensed developer. Whenever there is a need for a developer to develop ABP Commercial products on a third machine, an e-mail should be sent to license@abp.io explaining the situation, and we will then make the appropriate allocation in our system.",
"RefundPolicy": "Do you have a refund policy?",
- "RefundPolicyExplanation": "You can request a refund within 30 days of your license purchase. The Business and Enterprise license types have source-code download option, therefore we provide a 60% refund within 30 days for Business and Enterprise licenses. In addition, no refunds are made for renewals and second license purchases.",
+ "RefundPolicyExplanation": "You can request a refund within 30 days of your license purchase. The Business and Enterprise license types have source-code download options; therefore, we provide a 60% refund within 30 days for Business and Enterprise licenses. In addition, no refunds are made for renewals and second license purchases.",
"HowCanIRefundVat": "How can I refund VAT?",
"HowCanIRefundVatExplanation1": "If you made the payment using 2Checkout, you can refund VAT via your 2Checkout account:",
"HowCanIRefundVatExplanation2": "Log in to your 2Checkout account",
"HowCanIRefundVatExplanation3": "Find the appropriate order and press \"Refund Belated VAT\" (enter your VAT ID)",
"HowCanIGetMyInvoice": "How can I get my invoice?",
- "HowCanIGetMyInvoiceExplanation": "There are 2 payment gateways for purchasing a license: Iyzico and 2Checkout. If you purchase your license through the 2Checkout gateway, it sends the PDF invoice to your email address, check out 2Checkout invoicing. If you purchase through the Iyzico gateway, with a custom purchase link or via a bank wire transfer, we will prepare and send your invoice. You can request or download your invoice from the organization management page. Before contacting us for the invoice, check your organization management page!",
+ "HowCanIGetMyInvoiceExplanation": "There are 2 payment gateways for purchasing a license: Iyzico and 2Checkout. If you purchase your license through the 2Checkout gateway, it sends the PDF invoice to your email address; check out 2Checkout invoicing. If you purchase through the Iyzico gateway, with a custom purchase link or via a bank wire transfer, we will prepare and send your invoice. You can request or download your invoice from the organization management page. Before contacting us for the invoice, check your organization management page!",
"Forum": "Forum",
"SupportExplanation": "ABP Commercial license provides a premium forum support by a team consisting of the ABP Framework experts.",
"PrivateTicket": "Private Ticket",
@@ -245,15 +245,15 @@
"AbpSuiteExplanation1": "ABP Suite allows you to build web pages in a matter of minutes. It's a .NET Core Global tool that can be installed from the command line.",
"AbpSuiteExplanation2": "It can create a new ABP solution and generate CRUD pages from the database to the front-end. For technical overview see the document",
"FastEasy": "Fast & Easy",
- "AbpSuiteExplanation3": "ABP Suite allows you to easily create CRUD pages. You just need to define your entity and its properties, and let the rest to ABP Suite for you! ABP Suite generates all the necessary code for your CRUD page in a few seconds. It supports Angular, MVC and Blazor user interfaces.",
+ "AbpSuiteExplanation3": "ABP Suite allows you to easily create CRUD pages. You just need to define your entity and its properties and let the rest go to ABP Suite for you! ABP Suite generates all the necessary code for your CRUD page in a few seconds. It supports Angular, MVC and Blazor user interfaces.",
"RichOptions": "Rich Options",
"AbpSuiteExplanation4": "ABP Suite supports multiple UI options like Razor Pages and Angular.It also supports multiple databases like MongoDB and all databases supported by EntityFramework Core (MS SQL Server, Oracle, MySql, PostgreSQL, and other providers...).",
- "AbpSuiteExplanation5": "Good thing is that, you don't have to worry about those options. ABP Suite understands your project type and generates the code for your project and places the generated code in the correct place in your project.",
+ "AbpSuiteExplanation5": "The good thing is that you don't have to worry about those options. ABP Suite understands your project type and generates the code for your project and places the generated code in the correct place in your project.",
"SourceCode": "Source Code",
"AbpSuiteExplanation6": "ABP Suite generates the source code for you! It doesn't generate magic files to generate the web page. ABP Suite generates the source code for Entity, Repository, Application Service, Code First Migration, JavaScript/TypeScript and CSHTML/HTML and necessary Interfaces as well. ABP Suite also generates the code according to the Best Practices of software development, so you don't have to worry about the generated code's quality.",
"AbpSuiteExplanation7": "Since you have the source code of the building blocks of the generated CRUD page in the correct application layers, you can easily modify the source code and inject your custom/business logic to the generated code.",
"CrossPlatform": "Cross Platform",
- "AbpSuiteExplanation8": "ABP Suite is built with .NET Core and it is cross platform. It runs as a web application on your local computer. You can run it on Windows, Mac and Linux",
+ "AbpSuiteExplanation8": "ABP Suite is built with .NET Core, and it is cross-platform. It runs as a web application on your local computer. You can run it on Windows, Mac and Linux",
"OtherFeatures": "Other Features",
"OtherFeatures1": "Updates NuGet and NPM packages on your solution easily.",
"OtherFeatures2": "Regenerates already generated pages from scratch.",
@@ -291,7 +291,7 @@
"PerpetualLicense": "Perpetual license",
"UnlimitedServerDeployment": "Unlimited server deployment",
"YearUpgrade": "1 year upgrade",
- "YearPremiumForumSupport": "1 year premium forum support",
+ "YearPremiumForumSupport": "1-year premium forum support",
"ForumSupportIncidentCountYear": "Forum support incident count/year",
"PrivateTicketEmailSupport": "Private ticket & email support",
"BuyNow": "Buy Now",
@@ -395,7 +395,7 @@
"DowngradeLicensePlan": "Can I downgrade to a lower license plan in the future?",
"DowngradeLicensePlanExplanation": "You cannot downgrade your existing license plan. But you can purchase a new lower license plan and continue your development on the new license. After you purchase a lower license, you just need to login to your new license plan via ABP CLI command: ` abp login -o `.",
"LicenseTransfer": "Can a license be transferred from one developer to another?",
- "LicenseTransferExplanation": "Yes! When you purchase a license, you become the license holder, hence you will have access to the organization management page. An organization has owner and developer roles. Owners can manage the developer seats and assign developers. Each assigned developer will login via ABP CLI command into the system and will have development and support permissions.",
+ "LicenseTransferExplanation": "Yes! When you purchase a license, you become the license holder, hence you will have access to the organization management page. An organization has owner and developer roles. Owners can manage the developer seats and assign developers. Each assigned developer will log in via ABP CLI command into the system and will have development and support permissions.",
"UserOwnerDescription": "The 'Owner' of the organization is the admin of this account. He/she manages the organization by purchasing licenses and allocating developers. An 'Owner' cannot write code in the ABP Commercial projects, cannot download the ABP sample projects, and cannot ask questions on the support website. If you want to do all these, you have to add yourself as a developer too.",
"UserDeveloperDescription": "The 'Developers' can write code in the ABP Commercial projects, download the ABP sample projects, and ask questions on the support website. On the other hand, the 'Developers' cannot manage this organization.",
"RemoveCurrentUserFromOrganizationWarningMessage": "You are removing yourself from your own organization. You will no longer be able to manage this organization, do you confirm?",
@@ -475,14 +475,14 @@
"MultipleUIOptions": "Multiple UI Options",
"MultipleUIOptionsExplanation": "We love different ways to create the User Interface. This startup solution provides three different UI framework options for your business application.",
"MultipleDatabaseOptions": "Multiple Database Options",
- "MultipleDatabaseOptionsExplanation": "You have two database provider options (in addition to using both in a single application). Use Entity Framework Core to work with any relational database and optionally use Dapper when you need to write low-level queries for a better performance. MongoDB is another option if you need to use a document based NoSQL database. While these providers are well-integrated, abstracted and pre-configured, you can actually interact to any database system that you can use with .NET.",
+ "MultipleDatabaseOptionsExplanation": "You have two database provider options (in addition to using both in a single application). Use Entity Framework Core to work with any relational database and optionally use Dapper when you need to write low-level queries for better performance. MongoDB is another option if you need to use a document-based NoSQL database. While these providers are well-integrated, abstracted and pre-configured, you can actually interact with any database system that you can use with .NET.",
"ModularArchitectureExplanation2": "Modularity is a first-class citizen in the ABP.IO platform. All the application functionalities are split into well-isolated optional modules. The startup solution already comes with the fundamental ABP Commercial modules pre-installed. You can also create your own modules to build a modular system for your own application.",
"MultiTenancyForSaasBusiness": "Multi-Tenancy for your SaaS Business",
"MultiTenancyForSaasBusinessExplanation": "ABP Commercial provides a complete, end-to-end multi-tenancy system to create your SaaS (Software-as-a-Service) systems. It allows the tenants to share or have their own databases with on-the-fly database creation and migration system.",
"MicroserviceStartupSolution": "Microservice Startup Solution",
"MicroserviceArchitectureExplanation2": "You can get it for your next microservice system to take advantage of the pre-built base solution and distilled experience.",
"PreIntegratedTools": "Pre-Integrated to popular tools",
- "PreIntegratedToolsExplanation": "The solution is already integrated to the industry-standard tools and technologies, while you can always change them and integrate to your favorite tools.",
+ "PreIntegratedToolsExplanation": "The solution is already integrated into the industry-standard tools and technologies, while you can always change them and integrate to your favorite tools.",
"SingleSignOnAuthenticationServer": "Single Sign-on Authentication Server",
"SingleSignOnAuthenticationServerExplanation": "The solution has an authentication server application that is used by the other applications as a single sign-on server with the API access management features. It is based on the IdentityServer.",
"WebAppsWithGateways": "2 Web App with 2 API Gateways",
@@ -508,7 +508,7 @@
"Note": "Note",
"AdditionalNote": "Additional Note",
"OnboardingTrainingFaqTitle": "Do you have ABP onboarding training?",
- "OnboardingTrainingFaqExplanation": "Yes, we have ABP Training Services to help you get your ABP project started fast. You will learn about ABP from an ABP core team member, and you will get the skills to begin your ABP project. In the onboarding training, we will explain how to set up your development environment, install the required tools, create a fully functional CRUD page. The training will be live and the Zoom application will be used, and we are open to using other online meeting platforms. The language of the training will be English. You can also ask your questions about ABP during the sessions. A convenient time and date will be planned for both parties. To get more information, contact us at info@abp.io.",
+ "OnboardingTrainingFaqExplanation": "Yes, we have ABP Training Services to help you get your ABP project started fast. You will learn about ABP from an ABP core team member, and you will get the skills to begin your ABP project. In the onboarding training, we will explain how to set up your development environment, install the required tools, and create a fully functional CRUD page. The training will be live, and the Zoom application will be used, we are open to using other online meeting platforms. The language of the training will be English. You can also ask your questions about ABP during the sessions. A convenient time and date will be planned for both parties. To get more information, contact us at info@abp.io.",
"AddBasket": "Add to Basket",
"SendTrainingRequest": "Send Training Request",
"OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* The English version of this document is the most up-to-date, and the English version will prevail in any dispute.",
@@ -523,25 +523,25 @@
"Pricing_Page_Hint4": "ABP Suite is a tool to assist your development to improve your productivity. It supports generating CRUD pages and creating new projects.",
"Pricing_Page_Hint5": "You can use all the pre-built modules in your applications.",
"Pricing_Page_Hint6": "You can use all the pre-built themes in your applications.",
- "Pricing_Page_Hint7": "A startup template is a Visual Studio solution to make you jump-start to your project. All fundamental modules are added and pre-configured for you.",
- "Pricing_Page_Hint8": "Mastering ABP Framework e-book explains how to implement .NET solutions with best practices. It is sold on Amazon.com and you can download the book for free with your license.",
+ "Pricing_Page_Hint7": "A startup template is a Visual Studio solution to make you jump-start your project. All fundamental modules are added and pre-configured for you.",
+ "Pricing_Page_Hint8": "Mastering ABP Framework e-book explains how to implement .NET solutions with best practices. It is sold on Amazon.com, and you can download the book for free with your license.",
"Pricing_Page_Hint9": "You can download the source-code of any module. You may want to add the source code to your solution to make radical changes or just keep it for yourself for security reasons.",
"Pricing_Page_Hint10": "Licenses are for a lifetime. That means you can continue to develop your application forever. Accessing to the latest version and getting support are granted within the license period (1 year unless you renew it).",
"Pricing_Page_Hint11": "No restrictions on deployment! You can deploy to as many servers as you want, including the cloud services or on-premises.",
- "Pricing_Page_Hint12": "You can update the modules, themes and tools to the latest version within your active license period. After your license expires, you need to renew it, to continue to get updates for bug fixes, new features and enhancements.",
+ "Pricing_Page_Hint12": "You can update the modules, themes and tools to the latest version within your active license period. After your license expires, you need to renew it to continue to get updates for bug fixes, new features and enhancements.",
"Pricing_Page_Hint13": "You can get the premium support for one year (you can renew your license to extend it).",
"Pricing_Page_Hint14": "Team and Business licenses have incident/question count limit. If you buy additional developer licenses, your incident limit increases by {0} (for the Team License) or {1} (for the Business License) per developer.",
- "Pricing_Page_Hint15": "Only Enterprise License includes private support. You can send e-mail directly to the ABP Team or ask questions on support.abp.io with a private ticket option. The private tickets are not visible to the public.",
+ "Pricing_Page_Hint15": "Only Enterprise License includes private support. You can send an e-mail directly to the ABP Team or ask questions on support.abp.io with a private ticket option. The private tickets are not visible to the public.",
"Pricing_Page_Hint16": "You can download the source-code of all ABP themes. You may want to add the source code to your solution to make radical changes or just keep it for yourself for security reasons.",
"Pricing_Page_Testimonial_1": "ABP Commercial allowed SC Ventures to deliver a bank-grade multi-tenant silo-database SaaS platform in 9 months to support the accounts receivable / accounts payable supply chain financing of significant value invoices from multiple integrated anchors. The modularity of ABP made it possible for the team to deliver in record time, pass all VAPT, and deploy the containerized microservices stack via full CI/CD and pipelines into production.",
- "Pricing_Page_Testimonial_2": "We see the value of using ABP Commercial to reduce the overhead of custom development projects. And the team is able to unify the code pattern in different project streams. We see more potential in the framework for us to build new features faster than before. We trust we will be constantly seeing the value of leveraging ABP Commercial.",
- "Pricing_Page_Testimonial_3": "We love ABP. We don't have to write everything from scratch. We start from out-of-the-box features and just focus on what we really need to write. Also, ABP is well-architected and the code is high quality with fewer bugs. If we would have to write everything we needed on our own, we might have to spend years. Once more things we like is that the new version, or issue fixing, or improvement come out very soon every other week. We don't wait too long.",
+ "Pricing_Page_Testimonial_2": "We see the value of using ABP Commercial to reduce the overhead of custom development projects. The team is able to unify the code pattern in different project streams. We see more potential in the framework for us to build new features faster than before. We trust we will be constantly seeing the value of leveraging ABP Commercial.",
+ "Pricing_Page_Testimonial_3": "We love ABP. We don't have to write everything from scratch. We start from out-of-the-box features and just focus on what we really need to write. Also, ABP is well-architected and the code is high quality with fewer bugs. If we had to write everything we needed on our own, we might have to spend years. One more thing we like is that the new version, issue fixing, or improvement comes out very soon every other week. We don't wait too long.",
"Pricing_Page_Testimonial_4": "ABP Commercial is a fantastic product would recommend. Commercial products to market for our customers in a single configurable platform. The jump starts that the framework and tooling provide any team is worth every cent. ABP Commercial was the best fit for our needs.",
"Pricing_Page_Testimonial_5": "ABP Framework is not only a framework, but it is also a guide for project development/management, because it provides DDD, GenericRepository, DI, Microservice, and Modularity training. Even if you are not going to use the framework itself, you can develop yourself with docs.abp.io which is well and professionally prepared (OpenIddict, Redis, Quartz etc.). Because many things are pre-built, it shortens project development time significantly (Such as login page, exception handling, data filtering, seeding, audit logging, localization, auto API controller etc.). As an example from our application, I have used Local Event Bus for stock control. So, I am able to manage order movements by writing stock handler. It is wonderful not to lose time for CreationTime, CreatorId. They are being filled automatically.",
- "Pricing_Page_Testimonial_6": "ABP Framework is a good framework but it needs time to understand the different layers, classes, and libraries it uses (specially ABP). I spent a lot of time reading the code base, but ABP Commercial saved us time to create the project specialty entities (AR) and the repository linked to each of them. I liked also the approach used in ABP is very mature, we know is based on DDD and monolith.",
- "Pricing_Page_Testimonial_7": "As a startup, we need to iterate quickly and spend minimal time on boilerplate and non-core features.\nOur engineers range from highly experienced to junior engineers, we needed a common understanding and a way to share technical and domain knowledge, ABP allowed us to do this due to their great guides and documentation. \nThere are things we haven't had to worry about since they work out of the box with ABP. \nABP helped us streamline rapid prototyping and development, less than 4 weeks from feature inception to production. With all its premium features included in the license, ABP has given us, \"Startup in a Box\" on the Software Engineering Side.",
+ "Pricing_Page_Testimonial_6": "ABP Framework is a good framework but it needs time to understand the different layers, classes, and libraries it uses (especially ABP). I spent a lot of time reading the code base, but ABP Commercial saved us time in creating the project specialty entities (AR) and the repository linked to each of them. I liked also the approach used in ABP is very mature; we know is based on DDD and monolith.",
+ "Pricing_Page_Testimonial_7": "As a startup, we need to iterate quickly and spend minimal time on boilerplate and non-core features.\nOur engineers range from highly experienced to junior engineers, and we needed a common understanding and a way to share technical and domain knowledge, ABP allowed us to do this due to their great guides and documentation. \nThere are things we haven't had to worry about since they work out of the box with ABP. \nABP helped us streamline rapid prototyping and development, less than 4 weeks from feature inception to production. With all its premium features included in the license, ABP has given us, \"Startup in a Box\" on the Software Engineering Side.",
"Pricing_Page_Testimonial_8": "I would recommend ABP commercial to all those who want to expand the range of products available to their customers. It's fantastic when need to use a distributed enterprise environment (Angular, WPF, Win&Linux). In addition to their products, we love their support, which makes our job faster and easier. We already know that we have found a great partner for the future who will support us in expanding our business.",
- "Pricing_Page_Testimonial_9": "We are a company of 2 employees that's been in business for over 20 years.\nIn terms of our experience with ABP Commercial, we were approached by a client who requested that we develop a new human resources application in a modern environment to replace their 25-year-old Access application. We decided to transition from a desktop solution to a web-based one.\n\nAt the time, we had very little knowledge of web applications and .NET but we stumbled upon ABP Commercial, and with the help of ABP Framework, technical documentation, and ABP Suite, we were able to not only develop the application to the client's specifications but also successfully work within a .NET environment within a year.",
+ "Pricing_Page_Testimonial_9": "We are a company of 2 employees that's been in business for over 20 years.\nIn terms of our experience with ABP Commercial, we were approached by a client who requested that we develop a new human resources application in a modern environment to replace their 25-year-old Access application. We decided to transition from a desktop solution to a web-based one.\n\nAt the time, we had very little knowledge of web applications and .NET, but we stumbled upon ABP Commercial, and with the help of ABP Framework, technical documentation, and ABP Suite, we were able to not only develop the application to the client's specifications but also successfully work within a .NET environment within a year.",
"AbpBookDownloadArea_ClaimYourEBook": "Claim your Mastering ABP Framework E-Book",
"AddMemberModal_Warning_1": "If the username you are trying to add doesn't exist in the system, please ask your team member to register on {0} and share the username of his/her account with you.",
"MyOrganizations_Detail_WelcomeMessage": "Welcome to your organization, {0}",
@@ -567,7 +567,7 @@
"TotalPrice": "Total Price",
"ThereIsNoInvoice": "There is no invoice",
"MyOrganizations_Detail_PaymentProviderInfo": "If you have purchased your license through {0} gateway, it sends the PDF invoice to your email address, see {0} invoicing.",
- "MyOrganizations_Detail_PayUInfo": "If you have purchased through the PayU gateway, click the \"Request Invoice\" button and fill in the billing information.",
+ "MyOrganizations_Detail_PayUInfo": "If you have purchased through the Iyzico gateway, click the \"Request Invoice\" button and fill in the billing information.",
"MyOrganizations_Detail_ConclusionInfo": "Your invoice request will be concluded within {0} business days.",
"ExtendYourLicense": "Extend your {0} license",
"Continue": "Continue",
@@ -646,7 +646,7 @@
"UpgradePaymentInfoSection_ExtendMyLicenseForOneYear": "Yes, extend my license expiration date for 1 year.",
"UpgradePaymentInfoSection_WantToExtendLicense": "Do you want to extend your license for 1 more year?",
"UpgradePaymentInfoSection_UpgradingWillNotExtendLicense": "Upgrading will not extend your license expiration date!",
- "UpgradePaymentInfoSection_LicenseUpgradeDescription": "By upgrading your license, you will promote to a higher license type which will allow you to get additional benefits. See the license comparison table to check the differences between the license types.",
+ "UpgradePaymentInfoSection_LicenseUpgradeDescription": "By upgrading your license, you will be promoted to a higher license type, which will allow you to get additional benefits. See the license comparison table to check the differences between the license types.",
"Landing_Page_CustomerStories": "Customer Stories",
"Landing_Page_OurGreatCustomers": "Our Great Customers",
"Landing_Page_WebApplicationFramework": "Web Application Framework",
@@ -666,8 +666,8 @@
"Landing_Page_DocsModule": "Docs",
"Landing_Page_FileManagementModule": "File Management",
"Landing_Page_CustomerStory_1": "ABP Commercial allowed SC Ventures to deliver a bank-grade multi-tenant silo-database SaaS platform in 9 months to support the accounts receivable / accounts payable supply chain financing of significant value invoices from multiple integrated anchors. The modularity of ABP made it possible for the team to deliver in record time, pass all VAPT, and deploy the containerized microservices stack via full CI/CD and pipelines into production.",
- "Landing_Page_CustomerStory_2": "We see the value of using ABP Commercial to reduce the overhead of custom development projects. And the team can unify the code pattern in different project streams. We see more potential in the framework for us to build new features faster than before. We trust we will be constantly seeing the value of leveraging ABP Commercial.",
- "Landing_Page_CustomerStory_3": "We love ABP. We don't have to write everything from scratch. We start from out-of-the-box features and just focus on what we really need to write. Also, ABP is well-architected and the code is high quality with fewer bugs. If we would have to write everything we needed on our own, we might have to spend years. Once more thing we like is that the new version, or issue fixing, or improvement comes out very soon\n every other week. We don't wait too long.",
+ "Landing_Page_CustomerStory_2": "We see the value of using ABP Commercial to reduce the overhead of custom development projects. The team can unify the code pattern in different project streams. We see more potential in the framework for us to build new features faster than before. We trust we will be constantly seeing the value of leveraging ABP Commercial.",
+ "Landing_Page_CustomerStory_3": "We love ABP. We don't have to write everything from scratch. We start from out-of-the-box features and just focus on what we really need to write. Also, ABP is well-architected and the code is high quality with fewer bugs. If we had to write everything we needed on our own, we might have to spend years. One more thing we like is that the new version, or issue fixing, or improvement comes out very soon\n every other week. We don't wait too long.",
"Landing_Page_CustomerStory_4": "ABP Commercial is a fantastic product would recommend. Commercial products to market for our customers in a single configurable platform. The jump starts that the framework and tooling provide any team is worth every cent. ABP Commercial was the best fit for our needs.",
"Landing_Page_AdditionalServices": "Custom or volume license, onboarding, live training & support, custom project development, porting existing projects and more...",
"Landing_Page_IncludedDeveloperLicenses": "Included {0} developer licenses",
@@ -715,7 +715,7 @@
"Landing_Page_DocsModuleDescription_8": "Links to the file on GitHub, so anyone can easily contribute by clicking to the Edit link.",
"Landing_Page_DocsModuleDescription_9": "In addition to the GitHub source, allows to simply use a folder as the documentation source.",
"Landing_Page_FileManagementModuleDescription_1": "Upload, download and organize files in a hierarchical folder structure.",
- "Landing_Page_FileManagementModuleDescription_2": "This module is used to upload, download and organize files in a hierarchical folder structure. It is also compatible to multi-tenancy and you can determine total size limit for your tenants.",
+ "Landing_Page_FileManagementModuleDescription_2": "This module is used to upload, download and organize files in a hierarchical folder structure. It is also compatible with multi-tenancy and you can determine the total size limit for your tenants.",
"Landing_Page_FileManagementModuleDescription_3": "This module is based on the BLOB Storing system, so it can use different storage providers to store the file contents.",
"Landing_Page_IdentityModuleDescription_1": "This module implements the User and Role system of an application;",
"Landing_Page_IdentityModuleDescription_2": "Built on the Microsoft's ASP.NET Core Identity library.",
@@ -770,7 +770,7 @@
"YourFavoritePages": "Your favorite pages at your reach",
"YourFavoritePagesDescription": "Easily add or remove the page from favorites by clicking the star icon in the upper right corner of the page.",
"BreadCrumbs": "Breadcrumb for seamless switching",
- "BreadCrumbsDescription": "Using Breadcrumb, you can switch to the pages at the same level with one-click, even when the left menu is closed, and it works on tablet and mobile responsive!",
+ "BreadCrumbsDescription": "Using Breadcrumb, you can switch to the pages at the same level with one click, even when the left menu is closed, and it works on tablet and mobile responsive!",
"YourMenu": "Your menu as you wish",
"YourMenuDescription": "Customize the directly clickable icons and dropdown boxes on the user menu as you wish. The user menu is completely customizable for your needs",
"RtlSupport": "RTL support for your language",
@@ -793,10 +793,10 @@
"IndependentLayoutDescription1": "LeptonX's layout infrastructure was designed completely separate from the content.",
"IndependentLayoutDescription2": "This means that you can freely design your project with a content structure other than Bootstrap if you want.",
"MostUsedLibraries": "Most used libraries integrated with LeptonX",
- "MostUsedLibrariesDescription1": "LeptonX contains your most used libraries. It allows you to use libraries such as ApexCharts, DataTables, DropZone, FullCalender, JSTree, Select2, Toastr effortlessly.",
+ "MostUsedLibrariesDescription1": "LeptonX contains your most used libraries. It allows you to use libraries such as ApexCharts, DataTables, DropZone, FullCalender, JSTree, Select2, and Toastr effortlessly.",
"MostUsedLibrariesDescription2": "LeptonX also supports MVC Angular and Blazor-specific libraries.",
"CreateAndCustomize": "Create and customize the pages you need in seconds with LeptonX custom pages",
- "CreateAndCustomizeDescription": "By using LeptonX Theme you also have access to many pre-made html pages. These include many pages such as login page, blog, FAQ, subscription list, invoice, pricing, and file management.",
+ "CreateAndCustomizeDescription": "By using LeptonX Theme you also have access to many pre-made HTML pages. These include many pages such as login page, blog, FAQ, subscription list, invoice, pricing, and file management.",
"LeptonThemeForAdmin": "Lepton Theme for your admin dashboard by",
"LeptonThemeForAdminDescription": "Lepton Theme is still available and will be maintained. If you want to switch to LeptonX Theme as a Lepton Theme user, you can see the documentation to learn how-to.",
"LeptonCompatibleWith": "Lepton Theme is compatible with",
@@ -886,7 +886,7 @@
"DevelopYourSolution_Description5": "ABP completely automates \n unit of work (for database connection and transaction management), \n exception handling, \n validation\n and audit logging. It provides many more building blocks to simplify your daily development tasks and focus on your own code while creating production-ready \n applications.",
"DevelopYourSolution_Description6": "You can imagine how much that code block can be long and complicated if you would do it all manually.",
"SuiteCrudGenerationInFewSeconds": "In addition to hand coding your solution, you can create fully working advanced CRUD pages in a few minutes using the ABP Suite tooling. It generates the code into your solution, so you can fine-tune it based on your custom requirements.",
- "DeployAnywhere_Description1": "At the end of the day, you have a pure .NET solution. You can deploy your solution to your own server, to a cloud platform, Kubernetes or anywhere you want. You can deploy to as many servers as you want. ABP is a deployment environment agnostic tool.",
+ "DeployAnywhere_Description1": "At the end of the day, you have a pure .NET solution. You can deploy your solution to your own server, to a cloud platform, to Kubernetes or anywhere you want. You can deploy to as many servers as you want. ABP is a deployment environment agnostic tool.",
"ExpertiseAbpFramework": "Expertise the ABP Framework",
"ExpertiseAbpFramework_Description1": "Want to go beyond basics and get expertise with the ABP.IO Platform?",
"FreeDownload": "Free Download",
@@ -897,7 +897,7 @@
"OtherModules": "Other Modules",
"OtherModules_Description1": "Account, Audit Logging, Chat, CMS Kit, File Management, Forms, GDPR, Identity, Language Management, Payment, Saas and more...",
"HowItWorks_DatabaseProviderOptions": "Database provider options",
- "SeeFAQ" : "See FAQ",
+ "SeeFAQ": "See FAQ",
"ReleaseLogs": "Release Logs",
"ReleaseLogs_Tag": "{0} Release Logs",
"ReleaseLogs_Pr": "Pull Request #{0} - {1}",
@@ -937,18 +937,18 @@
"ThankYou!": "Thank you!",
"SendBetaRequest": "Send Beta Request",
"YouJoinedTheBetaTesterProgram": "You joined the ABP Studio beta tester program.",
- "PricingExplanation2": "30 days money back guarantee *. Learn more",
+ "PricingExplanation2": "30 days money back guarantee — Learn more",
"MoneyBackGuaranteeText": "* 30-day money-back guarantee on all licenses! 100% refund on Team, 60% refund on Business and Enterprise licenses within 30 days.",
"MobileApplicationStartupTemplates": "Mobile Application Startup Templates",
"MobileApplicationStartupTemplates_Description1": "Integrated mobile application startup templates for your ABP Commercial solutions.",
"CreatePowerfulLineOfBusinessApplicationsUsingABPMobileStartupTemplates": "Create Powerful line-of-business Applications using ABP Mobile Startup Templates",
- "CreatePowerfulLineOfBusinessApplicationsUsingABPMobileStartupTemplates_Description1": "ABP Commercial provides two mobile application startup templates implemented with React Native and .NET MAUI. When you create your new ABP based solution, you will also have basic startup applications connected to your backend APIs.",
+ "CreatePowerfulLineOfBusinessApplicationsUsingABPMobileStartupTemplates_Description1": "ABP Commercial provides two mobile application startup templates implemented with React Native and .NET MAUI. When you create your new ABP-based solution, you will also have basic startup applications connected to your backend APIs.",
"CreatePowerfulLineOfBusinessApplicationsUsingABPMobileStartupTemplates_Description2": "The application has a pre-built authentication token cycle, multi-language support, multi-tenancy support, login, forgot password, profile management and a user management page. You can add your own business logic and customize it based on your requirements.",
"TwoFrameworkOptions": "Two Framework Options",
- "TwoFrameworkOptions_Description": "ABP provides both React Native and .NET MAUI mobile startup templates. This way you can choose the one that best suits your needs. Both apps reuse code at the highest rate between iOS and Android platforms.",
+ "TwoFrameworkOptions_Description": "ABP provides both React Native and .NET MAUI mobile startup templates. This way, you can choose the one that best suits your needs. Both apps reuse code at the highest rate between iOS and Android platforms.",
"PreIntegratedToYourBackend": "Pre-integrated to Your Backend",
"PreIntegratedToYourBackend_Description": "ABP Mobile applications are pre-integrated to your backend APIs. It gets a valid authentication token from the server and makes authenticated requests.",
- "MultiLanguage": "Multi - Language",
+ "MultiLanguage": "Multi-Language",
"MultiLanguage_Description": "It already supports more than 10 languages out of the box. You can also add next languages.",
"Arabic": "Arabic",
"Czech": "Czech",
@@ -966,7 +966,7 @@
"EngageAndRetainYourCustomersWithABPMobileApps_Description1": "Your customers want to manage their products and subscriptions from anywhere, anytime. That requires organizations to create mobile apps that enable customers to fulfill their requests quickly and seamlessly.",
"EngageAndRetainYourCustomersWithABPMobileApps_Description2": "With ABP Mobile apps, you can create high-quality native mobile apps for Android and iOS… Using a single codebase and without compromising on security, quality, or scalability.",
"OneCodeBaseMultipleDevices": "One Code-Base Multiple Devices",
- "OneCodeBaseMultipleDevices_Description": "ABP Mobile applications are cross-platform. They are ready to be installed and run on iOS and Android devices, and they adapt to different form-factors using a single code base. Developers only need to create the UI and front-end code once, there is no need to adapt the code for each device you want to support.",
+ "OneCodeBaseMultipleDevices_Description": "ABP Mobile applications are cross-platform. They are ready to be installed and run on iOS and Android devices, and they adapt to different form factors using a single code base. Developers only need to create the UI and front-end code once, there is no need to adapt the code for each device you want to support.",
"ComesWithTheSourceCode": "Comes with the Source-Code",
"ComesWithTheSourceCode_Description": "The mobile apps are provided with the source-code. Easily customize the UX/UI of your apps to meet branding guidelines.",
"Purchase_OneYearPrice": "1 Year Price",
@@ -982,7 +982,7 @@
"Summary": "Summary",
"TrainingPack": "Training pack",
"TrainingPackDiscount": "Training pack discount",
- "Purchase_OnboardingTraining_Description": "This live training package is discounted when purchase with the new license. This discounted price is valid only for new license purchases. Learn more ",
+ "Purchase_OnboardingTraining_Description": "This live training package is discounted when purchased with the new license. This discounted price is valid only for new license purchases. Learn more ",
"Purchase_Save": "{0}% Save {1} {2}",
"RemoveBasket": "Remove from basket",
"WhyABPIOPlatform?": "Why ABP.IO Platform?",
@@ -1001,35 +1001,35 @@
"ABPSOLUTION": "ABP SOLUTION",
"CreatingAnEmptySolution_ABPSOLUTION_Description": "ABP provides a well-architected, layered and production-ready startup solution based on the Domain Driven Design principles. The solution also includes a pre-configured unit and integration test projects for each layer.",
"CommonLibraries": "Common Libraries",
- "CommonLibraries_THEPROBLEM_Description": "Which libraries should you use to implement common requirements? The software development ecosystem is highly dynamic and it is hard to follow the latest tools, libraries, trends and approaches.",
- "CommonLibraries_ABPSOLUTION_Description": "ABP pre-integrates the popular, mature and up-to-date libraries into the solution. You don't spend time integrating them and talking to each other. They properly work out of the box.",
+ "CommonLibraries_THEPROBLEM_Description": "Which libraries should you use to implement common requirements? The software development ecosystem is highly dynamic, making it challenging to keep up with the latest tools, libraries, trends, and approaches.",
+ "CommonLibraries_ABPSOLUTION_Description": "ABP pre-integrates popular, mature, and up-to-date libraries into the solution. You don't need to spend time integrating them or making them communicate with each other. They work properly out of the box.",
"UITheme&Layout": "UI Theme & Layout",
- "UITheme&Layout_THEPROBLEM_Description": "When it comes to the UI, there are a lot of challenges, including preparing a foundation to create a responsive, modern and flexible UI kit with a consistent look & feel and tons of features (like left/top navigation menu, header, toolbar, footer, widgets and so.).",
- "UITheme&Layout_THEPROBLEM_Description2": "Even if you buy a pre-built theme, integrating it into your solution may take days of development. Upgrading such a theme is another problem. Most of the time, the theme's HTML/CSS structure is mixed with your UI code, and it is not easy to upgrade or change the theme later.",
- "UITheme&Layout_ABPSOLUTION_Description": "ABP Framework provides a theming system that makes your UI code independent from the theme. Themes are isolated, and they are NuGet/NPM packages. Installing or upgrading a theme is just a minute. While you can build your theme (or integrate an existing theme), ABP Commercial offers professional and modern themes.",
- "UITheme&Layout_ABPSOLUTION_Description2": "There are also UI component providers (like Telerik and DevExpress). But they only provide individual components. You are responsible for creating your own layout system. You can use such libraries in your ABP-based solutions just like in any other project.",
+ "UITheme&Layout_THEPROBLEM_Description": "When addressing UI concerns, a range of challenges surfaces. These include establishing the groundwork for a responsive, contemporary, and adaptable UI kit with a consistent appearance and a host of features like navigation menus, headers, toolbars, footers, widgets, and more.",
+ "UITheme&Layout_THEPROBLEM_Description2": "Even if you opt for a pre-designed theme, seamlessly integrating it into your project could demand days of development. An additional hurdle lies in upgrading such themes. Frequently, the theme's HTML/CSS structure becomes intertwined with your UI code, rendering future theme changes or upgrades intricate tasks. This interweaving of code and design complicates the flexibility of making adjustments down the line.",
+ "UITheme&Layout_ABPSOLUTION_Description": "ABP Framework offers a distinctive theming system that liberates your UI code from theme constraints. Themes exist in isolation, packaged as NuGet or NPM packages, making theme installation or upgrades a matter of minutes. While you retain the option to develop your custom theme or integrate an existing one, ABP Commercial presents a collection of polished and contemporary themes.",
+ "UITheme&Layout_ABPSOLUTION_Description2": "Additionally, there are UI component providers like Telerik and DevExpress. However, these providers primarily furnish individual components, placing the onus on you to establish your layout system. When working within ABP-based projects, you can seamlessly incorporate these libraries, similar to how you would in any other project.",
"TestInfrastructure": "Test Infrastructure",
- "TestInfrastructure_THEPROBLEM_Description": "Preparing a robust test environment takes time. You need to setup test projects in your solution, select the tools, mock the services and database, create the required base classes and utility services to reduce repeating code in the tests and so on.",
- "TestInfrastructure_ABPSOLUTION_Description": "ABP Startup Templates comes with the test projects already configured for you, and you can immediately write your first unit or integration test code on day 1.",
+ "TestInfrastructure_THEPROBLEM_Description": "Establishing a robust testing environment is a time-consuming endeavor. It involves setting up dedicated test projects within your solution, carefully selecting the necessary tools, creating service and database mocks, crafting essential base classes and utility services to minimize redundant code across tests, and addressing various related tasks.",
+ "TestInfrastructure_ABPSOLUTION_Description": "ABP Startup Templates arrive pre-equipped with configured test projects, streamlining the process for you. This means that from day one, you can readily commence writing your initial unit or integration test code without delay.",
"CodingStandards&Training": "Coding Standards & Training",
- "CodingStandards&Training_THEPROBLEM_Description": "Once you create the development-ready solution, you typically need to train the developers to explain the system and develop it with the same conventions in a standard and consistent way. Even if you train the developers, it is hard to prepare and maintain your documentation. Over time, every developer will write the code differently, and coding standards will begin to diverge.",
- "CodingStandards&Training_ABPSOLUTION_Description": "ABP solution is already well-defined and well-documented. Tutorials and best practice guides clearly explain how to make development on an ABP project.",
+ "CodingStandards&Training_THEPROBLEM_Description": "After you've set up the solution for development, you usually have to teach the developers how the system works and how to build it using the same agreed-upon methods. Even if you give them training, keeping the documentation up-to-date can be difficult. As time goes on, each developer might write code in their own way, causing the rules for writing code to become different from each other.",
+ "CodingStandards&Training_ABPSOLUTION_Description": "The ABP solution is already neatly organized and has clear explanations. Step-by-step tutorials and guides show you exactly how to work on an ABP project.",
"KeepingYourSolutionUpToDate": "Keeping Your Solution Up to Date",
"KeepingYourSolutionUpToDate_THEPROBLEM_Description": "After you start your development, you must keep track of the new versions of the libraries you use for upgrades & patches.",
"KeepingYourSolutionUpToDate_ABPSOLUTION_Description": "We regularly update all packages to the latest versions and test them before the stable release. When you update the ABP Framework, all its dependencies are upgraded to edge technology.",
"KeepingYourSolutionUpToDate_ABPSOLUTION_Description2": "Abp update CLI command automatically discovers and upgrades all ABP-dependant NuGet and NPM packages in a solution. With ABP, it is easier to stay with the latest versions.",
"DRY": "Don't Repeat Yourself!",
- "DRY_Description": "Creating a base solution takes significant time and requires well architectural experience. However, this is just the beginning! As you start developing, you will likely have to write lots of repetitive code; that would be great if all this could be handled automatically.",
+ "DRY_Description": "Creating a base solution takes significant time and requires good architectural experience. However, this is just the beginning! As you start developing, you will likely have to write lots of repetitive code; that would be great if all this could be handled automatically.",
"DRY_Description2": "ABP automates and simplifies repeating code as much as possible by following the convention over configuration principle. However, it doesn't restrict you when you need to switch to manual gear. The control is always in your hands.",
"Authentication": "Authentication",
- "Authentication_THEPROBLEM_Description": "Single Sign On, Active Directory / LDAP Integration, OpenIddict integration, social logins, two-factor authentication, forgot/reset password, email activation, new user registration, password complexity control, locking account on failed attempts, showing failed login attemps ... etc. We know that all these generic requirements are familiar to you. You are not alone!",
+ "Authentication_THEPROBLEM_Description": "Single Sign On, Active Directory / LDAP Integration, OpenIddict integration, social logins, two-factor authentication, forgot/reset password, email activation, new user registration, password complexity control, locking account on failed attempts, showing failed login attempts... etc. We know that all these generic requirements are familiar to you. You are not alone!",
"Authentication_ABPSOLUTION_Description": "ABP Framework and the commercial version provide all these standard stuff pre-implemented for you as a re-usable account module. You just enable and configure what you need.",
"CrossCuttingConcerns": "Cross-Cutting Concerns",
- "CrossCuttingConcerns_THEPROBLEM_Description": "Cross-Cutting Concerns are the fundamental repeating logic that should be implementedfor each use case. Some examples;",
+ "CrossCuttingConcerns_THEPROBLEM_Description": "Cross-Cutting Concerns are the fundamental repeating logic that should be implemented for each use case. Some examples;",
"CrossCuttingConcerns_THEPROBLEM_Description2": "Starting transactions, committing on success and rollback on errors.",
- "CrossCuttingConcerns_THEPROBLEM_Description3": "Handling and reporting exceptions, returning a proper error response to the clients and handling error cases on the client-side.",
- "CrossCuttingConcerns_THEPROBLEM_Description4": "Implementing authorization and validation, returning proper responses and handling these on the client-side.",
- "CrossCuttingConcerns_ABPSOLUTION_Description": "ABP Framework automates or simplifies all the common cross-cutting concerns. You only write code that matters for your business and ABP handles the rest by conventions.",
+ "CrossCuttingConcerns_THEPROBLEM_Description3": "Handling and reporting exceptions, returning a proper error response to the clients and handling error cases on the client side.",
+ "CrossCuttingConcerns_THEPROBLEM_Description4": "Implementing authorization and validation, returning proper responses and handling these on the client side.",
+ "CrossCuttingConcerns_ABPSOLUTION_Description": "ABP Framework automates or simplifies all the common cross-cutting concerns. You only write code that matters for your business, and ABP handles the rest by conventions.",
"ArchitecturalInfrastructure": "Architectural Infrastructure",
"ArchitecturalInfrastructure_THEPROBLEM_Description": "You typically need to build infrastructure to implement your architecture properly. For example, you generally implement the Repository pattern. You define some base classes to simplify and standardize to create entities, services, controllers and other objects.",
"ArchitecturalInfrastructure_ABPSOLUTION_Description": "ABP Framework provides all these and more out of the box. It is mature and well-documented.",
@@ -1042,37 +1042,39 @@
"EnterpriseApplicationRequirements_THEPROBLEM_Description6": "Enqueuing and executing background jobs.",
"EnterpriseApplicationRequirements_THEPROBLEM_Description7": "Handling multiple time zones in a global system.",
"EnterpriseApplicationRequirements_THEPROBLEM_Description8": "Sharing validation, localization, authorization logic between server and client.",
- "EnterpriseApplicationRequirements_ABPSOLUTION_Description": "ABP provides infrastructure to implement such requirements easily. Again, you don't spend your valuable time to re-implement all these again and again.",
+ "EnterpriseApplicationRequirements_ABPSOLUTION_Description": "ABP provides an infrastructure to implement such requirements easily. Again, you don't spend your valuable time to re-implement all these again and again.",
"GeneratingInitialCode&Tooling": "Generating Initial Code & Tooling",
- "GeneratingInitialCode&Tooling_THEPROBLEM_Description": "You will build many similar pages in a typical web application. Most of them will perform similar CRUD operations. It is very tedious and also error prone to repeatedly create such pages.",
+ "GeneratingInitialCode&Tooling_THEPROBLEM_Description": "You will build many similar pages in a typical web application. Most of them will perform similar CRUD operations. It is very tedious and also error-prone to repeatedly create such pages.",
"GeneratingInitialCode&Tooling_ABPSOLUTION_Description": "ABP Suite can generate a full-stack CRUD page for your entities in seconds. The generated code is layered and clean. All the standard validation and authorization requirements are implemented. Plus, unit test classes are generated. Once you get a fully running page, you can modify it according to your business requirements.",
"IntegratingTo3rdPartyLibrariesAndSystems": "Integrating to 3rd-Party Libraries and Systems",
- "IntegratingTo3rdPartyLibrariesAndSystems_THEPROBLEM_Description": "Most libraries are designed as low level, and you typically do some work to integrate them properly without repeating the same integration and configuration code everywhere in your solution. For example, assume you must use RabbitMQ to implement your distributed event bus. All you want to do is; send a message to a queue and handle the incoming messages. But you need to understand messaging patterns, queue and exchange details. To write an efficient code, you must create a pool to manage connections, clients and channels. You also must deal with exceptions, ACK messages, re-connecting to RabbitMQ on failures and more.",
+ "IntegratingTo3rdPartyLibrariesAndSystems_THEPROBLEM_Description": "Most libraries are designed as low level, and you typically do some work to integrate them properly without repeating the same integration and configuration code everywhere in your solution. For example, assume you must use RabbitMQ to implement your distributed event bus. All you want to do is; send a message to a queue and handle the incoming messages. But you need to understand messaging patterns, queues and exchange details. To write efficient code, you must create a pool to manage connections, clients and channels. You also must deal with exceptions, ACK messages, re-connecting to RabbitMQ on failures and more.",
"IntegratingTo3rdPartyLibrariesAndSystems_ABPSOLUTION_Description": "For example, ABP's RabbitMQ Distributed Event Bus integration abstracts all these details. You send and receive messages without the hustle and bustle. Do you need to write low-level code? No problem, you can always do that. ABP doesn't restrict you when you need to use low-level features of the library you are using.",
"WhyNotBuildYourOwnFramework?": "Why Not Build Your Own Framework?",
- "WhyNotBuildYourOwnFramework_THEPROBLEM_Description": "All the infrastructure, even in the most simple way, takes a lot of time to build, maintain and document. It gets bigger over time, and it becomes hard to maintain it in your solution. Separating these into a re-usable project is the starting point for building your own internal framework.",
+ "WhyNotBuildYourOwnFramework_THEPROBLEM_Description": "All the infrastructure, even in the simplest way, takes a lot of time to build, maintain and document. It gets bigger over time, and it becomes hard to maintain it in your solution. Separating these into a re-usable project is the starting point for building your own internal framework.",
"WhyNotBuildYourOwnFramework_THEPROBLEM_Description2": "Building, documenting, training and maintaining an internal framework is really hard. If you don't have an experienced, dedicated framework team, your internal framework rapidly becomes an undocumented legacy code that no one can understand and maintain anymore. On the other hand, these frameworks are generally developed by one or two developers in the team. And these fellows are becoming a knowledge silo. It is good for them but bad for the company because they are the project's single point of failure -SPOF-. Once they leave the company, the project dramatically goes down.",
"WhyNotBuildYourOwnFramework_ABPSOLUTION_Description": "ABP Framework is a community-driven, well-documented, mature and generic application framework. A team of highly experienced developers are working hard to keep it up-to-date, easy to understand and comfortable to use. Using such a stable framework makes you focus on your own business code and get help with the framework from experts whenever you need it.",
- "ArchitecturalInfrastructure_Description": "SaaS applications, modular or microservice systems are most used enterprise software models. Building such systems not only requires a good understanding and experience, but also requires a strong software infrastructure. Otherwise, you will find yourself spending a great effort to support these architectural details in your codebase.",
+ "ArchitecturalInfrastructure_Description": "SaaS applications, modular or microservice systems are most used enterprise software models. Building such systems not only requires a good understanding and experience but also requires a strong software infrastructure. Otherwise, you will find yourself spending a great effort to support these architectural details in your codebase.",
"Modularity": "Modularity",
"Modularity_THEPROBLEM_Description": "Building a truly modular system is not easy! All the aspects of the system (database, entities, APIs, UI pages/components) can be split into modules, and each module can be re-usable without others. The plain ASP.NET Core doesn't provide such a modular architecture. If you need it, you should think about it from scratch.",
"Modularity_ABPSOLUTION_Description": "The ABP Framework is born to be a modular application development structure. Every feature in the framework is developed to be compatible with modularity. Documentation and guides explain how to develop re-usable modules in a standard way.",
"SaaSMultiTenancy": "SaaS / Multi-Tenancy",
"SaaSMultiTenancy_THEPROBLEM_Description": "Multi-Tenancy is a common way to implement SaaS systems. However, implementing a consistent multi-tenant infrastructure may become complicated.",
- "SaaSMultiTenancy_ABPSOLUTION_Description": "ABP Framework provides a complete multi-tenant infrastructure and abstract complexity from your business code. Your application code will be mostly multi-tenancy aware while the ABP Framework automatically isolates the database, cache and other details of the tenants from each other. It supports single database, per tenant database and hybrid approaches. It properly configures the libraries like Microsoft Identity and OpenIddict, which are not normally multi-tenancy compatible.",
+ "SaaSMultiTenancy_ABPSOLUTION_Description": "ABP Framework provides a complete multi-tenant infrastructure and abstract complexity from your business code. Your application code will be mostly multi-tenancy aware, while the ABP Framework automatically isolates the database, cache and other details of the tenants from each other. It supports single database, per tenant database and hybrid approaches. It properly configures the libraries like Microsoft Identity and OpenIddict, which are not normally multi-tenancy compatible.",
"Microservices": "Microservices",
"Microservices_THEPROBLEM_Description": "Building a microservice system requires many infrastructure details: Authenticating and authorizing applications and microservices and implementing asynchronous messaging and synchronous (Rest/GRPC) communication patterns between microservices are the most fundamental issues.",
"Microservices_ABPSOLUTION_Description": "The ABP Framework provides services, guides, and samples to help you implement your microservice solution using the industry standard tools.",
"Microservices_ABPSOLUTION_Description2": "ABP Commercial even goes one step further and provides a complete startup template to kick-start your microservice solution.",
"PreBuiltModules": "Pre-Built Modules",
"PreBuiltModules_THEPROBLEM_Description": "All of us have similar but slightly different business requirements. However, we all should re-invent the wheel since no one's code can directly work in our solution. They are all embedded parts of a larger solution.",
- "PreBuiltModules_ABPSOLUTION_Description":"ABP Commercial modules provides a lot of re-usable application modules like payment, chat, file management, audit log reporting... etc. All of these modules are easily installed into your solution and directly work. We are constantly adding more modules.",
+ "PreBuiltModules_ABPSOLUTION_Description": "ABP Commercial modules provides a lot of re-usable application modules like payment, chat, file management, audit log reporting... etc. All of these modules are easily installed into your solution and directly work. We are constantly adding more modules.",
"PreBuiltModules_ABPSOLUTION_Description2": "All modules are designed as customizable for your business requirements. If you need complete control, you can download the full source code of any module and completely customize based on your specific business requirements.",
"ABPCommunity": "ABP Community",
- "ABPCommunity_Description": "Finally, Being in a big community where everyone follows similar coding styles and principles and shares a common infrastructure brings power when you have troubles or need help with design decisions. Since we write code similarly, we can help each other much better. ABP is a community-backed project with more then 10K stars on GitHub.",
+ "ABPCommunity_Description": "Finally, Being in a big community where everyone follows similar coding styles and principles and shares a common infrastructure brings power when you have troubles or need help with design decisions. Since we write code similarly, we can help each other much better. ABP is a community-backed project with more than 10K stars on GitHub.",
"ABPCommunity_Description2": "It is easy to share code or even re-usable libraries between ABP developers. A code snippet that works for you will also work for others. There are a lot of samples and tutorials that you can directly implement for your application.",
"ABPCommunity_Description3": "When you hire a developer who worked before with the ABP architecture will immediately understand your solution and start development in a very short time.",
"WhyAbpIo_Page_Title": "Why ABP.IO Platform?",
- "AbpStudio_Page_Title": "ABP Studio"
+ "AbpStudio_Page_Title": "ABP Studio",
+ "CampaignInfo": "Buy a new license or renew your existing license and get an additional 2 months at no additional cost! This offer is valid for all license plans. Ensure you take advantage of this limited-time promotion to expand your access to premium features and upgrades.",
+ "HurryUpLastDay": "Hurry Up! Last Day: {0}"
}
-}
+}
\ No newline at end of file
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
index e6b3cf46f7..f5a637676e 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
@@ -37,6 +37,7 @@
"ThisExtensionIsNotAllowed": "This extension is not allowed.",
"TheFileIsTooLarge": "The file is too large.",
"GoToThePost": "Go to the Post",
+ "GoToTheVideo": "Go to the Video",
"Contribute": "Contribute",
"OverallProgress": "Overall Progress",
"Done": "Done",
@@ -189,6 +190,35 @@
"SeeMoreVideos": "See more videos",
"DiscordPageTitle": "ABP Discord Community",
"ViewVideo": "View Video",
- "AbpCommunityTitleContent": "ABP Community - Open Source ABP Framework"
+ "AbpCommunityTitleContent": "ABP Community - Open Source ABP Framework",
+ "CommunitySlogan": "A unique community platform for ABP Lovers",
+ "RaffleIsNotActive": "Raffle is not active",
+ "YouAreAlreadyJoinedToThisRaffle": "You are already joined to this raffle",
+ "InvalidSubscriptionCode": "Invalid subscription code",
+ "Raffle:{0}": "Raffle: {0}",
+ "Join": "Join",
+ "Leave": "Leave",
+ "LoginToJoin": "Login to join",
+ "ToEnd:": "To end:",
+ "ToStart:": "To start:",
+ "days": "days",
+ "hrs": "hrs",
+ "min": "min",
+ "sec": "sec",
+ "Winners:": "Winners:",
+ "To{0}LuckyWinners": "to {0} lucky winner(s)",
+ "ActiveRaffles": "Active Raffles",
+ "UpcomingRaffles": "Upcoming Raffles",
+ "CompletedRaffles": "Completed Raffles",
+ "NoActiveRaffleTitle": "No active raffle is available at the moment.",
+ "NoActiveRaffleDescription": "No active raffle is available at the moment.",
+ "RaffleSubscriptionCodeInputMessage": "This raffle requires a registration code. Please enter the registration code below:",
+ "RaffleSubscriptionCodeInputErrorMessage": "The registration code is incorrect. Please try again.",
+ "GoodJob!": "Good Job!",
+ "RaffleJoinSuccessMessage": "You are successfully registered tor the raffle. You will be informed via email if you win the prize!",
+ "RaffleLoginAndRegisterMessage": "You should sign in to join a raffle. You can create an account for free if you haven't registered yet.",
+ "Ok": "Ok",
+ "SeeDetails": "See Details",
+ "WaitingForDraw": "Waiting for draw"
}
}
diff --git a/common.props b/common.props
index 1b052fb2f1..e956b17835 100644
--- a/common.props
+++ b/common.props
@@ -1,7 +1,7 @@
latest
- 7.4.0-rc.4
+ 8.0.0$(NoWarn);CS1591;CS0436https://abp.io/assets/abp_nupkg.pnghttps://abp.io/
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/POST.md b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/POST.md
new file mode 100644
index 0000000000..c7d8411886
--- /dev/null
+++ b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/POST.md
@@ -0,0 +1,287 @@
+# ABP.IO Platform 7.4 RC Has Been Released
+
+Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.4 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 v7.4! Thanks to all of you.
+
+## Get Started with the 7.4 RC
+
+Follow the steps below to try version 7.4.0 RC today:
+
+1) **Upgrade** the ABP CLI to version `7.4.0-rc.1` using a command line terminal:
+
+````bash
+dotnet tool update Volo.Abp.Cli -g --version 7.4.0-rc.1
+````
+
+**or install** it if you haven't before:
+
+````bash
+dotnet tool install Volo.Abp.Cli -g --version 7.4.0-rc.1
+````
+
+2) Create a **new application** with the `--preview` option:
+
+````bash
+abp new BookStore --preview
+````
+
+See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options.
+
+> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application.
+
+You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).
+
+## Migration Guides
+
+There are a few breaking changes in this version that may affect your application.
+Please see the following migration documents, if you are upgrading from v7.3 or earlier:
+
+* [ABP Framework 7.3 to 7.4 Migration Guide](https://docs.abp.io/en/abp/7.4/Migration-Guides/Abp-7_4)
+
+## What's New with ABP Framework 7.4?
+
+In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections:
+
+* Dynamic Setting Store
+* Introducing the `AdditionalAssemblyAttribute`
+* `CorrelationId` Support on Distributed Events
+* Database Migration System for EF Core
+* Other News
+
+### Dynamic Setting Store
+
+Prior to this version, it was hard to define settings in different microservices and centrally manage all setting definitions in a single admin application. To make that possible, we used to add project references for all the microservices' service contract packages from a single microservice, so it can know all the setting definitions and manage them.
+
+In this version, ABP Framework introduces the Dynamic Setting Store, which is an important feature that allows you to collect and get all setting definitions from a single point and overcome the setting management problems on microservices.
+
+> *Note*: If you are upgrading from an earlier version and using the Setting Management module, you need to create a new migration and apply it to your database because a new database table has been added for this feature.
+
+### Introducing the `AdditionalAssemblyAttribute`
+
+In this version, we have introduced the `AdditionalAssemblyAttribute` to define additional assemblies to be part of a module. ABP Framework automatically registers all the services of your module to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection). It finds the service types by scanning types in the assembly that define your module class. Typically, every assembly contains a separate module class definition and modules depend on each other using the `DependsOn` attribute.
+
+In some rare cases, your module may consist of multiple assemblies and only one of them defines a module class, and you want to make the other assemblies parts of your module. This is especially useful if you can't define a module class in the target assembly or you don't want to depend on that module's dependencies.
+
+In that case, you can use the `AdditionalAssembly` attribute as shown below:
+
+```csharp
+[DependsOn(...)] // Your module dependencies as you normally would do
+[AdditionalAssembly(typeof(IdentityServiceModule))] // A type in the target assembly (in another assembly)
+public class IdentityServiceTestModule : AbpModule
+{
+ ...
+}
+```
+
+With the `AdditionalAssembly` attribute definition, ABP loads the assembly containing the `IdentityServiceModule` class as a part of the identity service module. Notice that in this case, none of the module dependencies of the `IdentityServiceModule` are loaded. Because we are not depending on the `IdentityServiceModule`, instead we are just adding its assembly as a part of the `IdentityServiceTestModule`.
+
+> You can check the [Module Development Basics](https://docs.abp.io/en/abp/7.4/Module-Development-Basics) documentation to learn more.
+
+### `CorrelationId` Support on Distributed Events
+
+In this version, `CorrelationId` (a unique key that is used in distributed applications to trace requests across multiple services/operations) is attached to the distributed events, so you can relate events with HTTP requests and can trace all the related activities.
+
+ABP Framework generates a `correlationId` for the first time when an operation is started and then attaches the current `correlationId` to distributed events as an additional property. For example, if you are using the [transactional outbox or inbox pattern provided by ABP Framework](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#outbox-inbox-for-transactional-events), you can see the `correlationId` in the extra properties of the `IncomingEventInfo` or `OutgoingEventInfo` classes with the standard `X-Correlation-Id` key.
+
+> You can check [this issue](https://github.com/abpframework/abp/issues/16773) for more information.
+
+### Database Migration System for EF Core
+
+In this version, ABP Framework provides base classes and events to migrate the database schema and seed the database on application startup. This system works compatibly with multi-tenancy and whenever a new tenant is created or a tenant's database connection string has been updated, it checks and applies database migrations for the new tenant state.
+
+This system is especially useful to migrate databases for microservices. In this way, when you deploy a new version of a microservice, you don't need to manually migrate its database.
+
+You need to take the following actions to use the database migration system:
+
+* Create a class that derives from `EfCoreRuntimeDatabaseMigratorBase` class, override and implement its `SeedAsync` method. And lastly, execute the `CheckAndApplyDatabaseMigrationsAsync` method of your class in the `OnPostApplicationInitializationAsync` method of your module class.
+* Create a class that derives from `DatabaseMigrationEventHandlerBase` class, override and implement its `SeedAsync` method. Then, whenever a new tenant is created or a tenant's connection string is changed then the `SeedAsync` method will be executed.
+
+### Other News
+
+* [OpenIddict](https://github.com/openiddict/openiddict-core/tree/4.7.0) library has been upgraded to **v4.7.0**. See [#17334](https://github.com/abpframework/abp/pull/17334) for more info.
+* ABP v7.4 introduces the `Volo.Abp.Maui.Client` package, which is used by the MAUI mobile application in ABP Commercial. See [#17201](https://github.com/abpframework/abp/pull/17201) for more info.
+* In this version, the `AbpAspNetCoreIntegratedTestBase` class gets a generic type parameter, which expects either a startup class or an ABP module class. This allows us to use configurations from an ABP module or old-style ASP.NET Core Startup class in a test application class and this simplifies the test application project. See [#17039](https://github.com/abpframework/abp/pull/17039) for more info.
+
+## What's New with ABP Commercial 7.4?
+
+We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce new features coming with ABP Commercial 7.4.
+
+### Dynamic Text Template Store
+
+Prior to this version, it was hard to create text templates in different microservices and centrally manage them in a single admin application. For example, if you would define a text template in your ordering microservice, then those text templates could not be seen on the administration microservice because the administration microservice would not have any knowledge about that text template (because it's hard-coded in the ordering microservice).
+
+For this reason, in this version, the Dynamic Text Template Store has been introduced to make the [Text Template Management module](https://docs.abp.io/en/commercial/latest/modules/text-template-management) compatible with microservices and distributed systems. It allows you to store and get all text templates from a single point. Thanks to that, you can centrally manage the text templates in your admin application.
+
+> *Note*: If you are upgrading from an earlier version and are using the Text Template Management module, you need to create a new migration and apply it to your database.
+
+To enable the dynamic template store, you just need to configure the `TextTemplateManagementOptions` and set the `IsDynamicTemplateStoreEnabled` as true in your module class:
+
+```csharp
+Configure(options =>
+{
+ options.IsDynamicTemplateStoreEnabled = true;
+});
+```
+
+Notice this is only needed in the microservice where you centrally manage your text template contents. So, typically you would use the configuration above in your administration microservice. Other microservices automatically save their text template contents to the central database.
+
+### Suite: Custom Code Support
+
+In this version, we have implemented the custom code support in Suite. This allows you to customize the generated code-blocks and preserve your custom code changes in the next CRUD Page Generation in Suite. ABP Suite specifies hook-points to allow adding custom code blocks. Then, the code that you wrote to these hook points will be respected and will not be overridden in the next entity generation.
+
+
+
+To enable custom code support, you should check the *Customizable code* option in the crud page generation page. When you enable the custom code support, you will be seeing some hook-points in your application.
+
+For example, on the C# side, you'll be seeing some abstract classes and classes that derive from them (for entities, application services, interfaces, domain services, and so on...). You can write your custom code in those classes (`*.Extended.cs`) and the next time when you need to re-generate the entity, your custom code will not be overridden (only the base abstract classes will be re-generated and your changes on Suite will be respected):
+
+Folder structure | Book.Extended.cs
+:-------------------------:|:-------------------------:
+ | 
+
+> *Note*: If you want to override the entity and add custom code, please do not touch the code between `...` placeholders, because the constructor of the entity should be always re-generated in case of a new property added.
+
+On the UI side, you can see the *comment placeholders* on the pages for MVC & Blazor applications. These are hook-points provided by ABP Suite and you can write your custom code between these comment sections:
+
+Folder structure | Books/Index.cshtml
+:-------------------------:|:-------------------------:
+ | 
+
+### MAUI & React Native UI Revisions
+
+In this version, we have revised MAUI & React Native mobile applications and added new pages, functionalities and made improvements on the UI side.
+
+
+
+For example, in the MAUI application, we have implemented the following functionalities and changed the UI completely:
+
+* **User Management Page**: Management page for your application users. You can search, add, update, or delete users of your application.
+* **Tenants**: Management page for your tenants.
+* **Settings**: Management page for your application settings. On this page, you can change **the current language**, **the profile picture**, **the current password**, or/and **the current theme**.
+
+Also, we have aligned the features on both of these mobile options (MAUI & React Native) and showed them in the ["ABP Community Talks 2023.5: Exploring the Options for Mobile Development with the ABP Framework"](https://community.abp.io/events/mobile-development-with-the-abp-framework-ogtwaz5l).
+
+> If you have missed the event, you can watch from 👉 [here](https://www.youtube.com/watch?v=-wrdngeKgZw).
+
+### New LeptonX Theme Features
+
+In the new version of LeptonX Theme, which is v2.4.0-rc.1, there are some new features that we want to mention.
+
+#### Mobile Toolbars
+
+The [Toolbar System](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) is used to define *toolbars* on the user interface. Modules (or your application) can add items to a toolbar, then the UI themes can render the toolbar on the layout.
+
+LeptonX Theme extends this system even further and introduces mobile toolbars with this version. You can create a component and add it as a mobile toolbar as below:
+
+```csharp
+public class MyToolbarContributor : IToolbarContributor
+{
+ public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
+ {
+ if (context.Toolbar.Name == LeptonXToolbars.MainMobile)
+ {
+ context.Toolbar.Items.Add(new ToolbarItem(typeof(ShoppingCardToolbarComponent)));
+
+ //other mobile toolbars...
+ }
+
+ return Task.CompletedTask;
+ }
+}
+```
+
+Then, the LeptonX Theme will render these mobile toolbars like in the figure below:
+
+
+
+> **Note**: The Angular UI hasn't been completed yet. We aim to complete it as soon as possible and include it in the next release.
+
+#### New Error Page Designs
+
+In this version, we have implemented new error pages. Encounter a fresh look during error situations with the 'New Error Page Designs,' providing informative and visually appealing error displays that enhance user experience:
+
+
+
+#### Fluid Layout
+
+In this version, LeptonX Theme introduces the fresh-looking **Fluid Layout**, which is a layout that lets you align elements so that they automatically adjust their alignment and proportions for different page sizes and orientations.
+
+
+
+> You can visit [the live demo of LeptonX Theme](https://x.leptontheme.com/side-menu) and try the Fluid Layout now!
+
+### Check & Move Related Entities on Deletion/Demand
+
+In application modules, there are some entities that have complete relationships with each other such as role-user relations. In such cases, it's a typical requirement to check & move related entities that have a relation with the other entity that is about to be deleted.
+
+For example, if you need to delete an edition from your system, you would typically want to move the tenant that is associated with that edition. For this purpose, in this version, ABP Commercial allows you to move related entities on deletion/demand.
+
+
+
+Currently, this feature is implemented for SaaS and Identity Pro modules and for the following relations:
+
+* Edition - Tenant
+* Role - User
+* Organization Unit - User
+
+Also, it's possible to move the related associated-records before deleting the record. For example, you can move all tenants from an edition as shown in the figure below:
+
+"Move all tenants" action | "Move all tenants" modal
+:-------------------------:|:-------------------------:
+ | 
+
+### CMS Kit Pro: Page Feedback
+
+In this version, the **Page Feedback** feature has been added to the [CMS Kit Pro](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index) module. This feature allows you to get feedback from a page in your application.
+
+This is especially useful if you have content that needs feedback from users. For example, if you have documentation or a blog website, it's a common requirement to assess the quality of the articles and get feedback from users. In that case, you can use this feature:
+
+
+
+### Chat Module: Deleting Messages & Conversations
+
+In this version, the [Chat Module](https://docs.abp.io/en/commercial/latest/modules/chat) allows you to delete individual messages or a complete conversation.
+
+You can enable or disable the message/conversation deletion globally on your application:
+
+
+
+> **Note**: The Angular UI hasn't been completed yet. We aim to complete it as soon as possible and include it in the next release.
+
+### Password Complexity Indicators
+
+In this version, ABP Framework introduces an innovative ["Password Complexity Indicator"](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) feature, designed to enhance security and user experience. This feature dynamically evaluates and rates the strength of user-generated passwords, providing real-time feedback to users as they create or update their passwords. By visually indicating the complexity level, users are guided toward crafting stronger passwords that meet modern security standards.
+
+
+
+You can check the [Password Complexity Indicator Angular documentation](https://docs.abp.io/en/commercial/7.4/ui/angular/password-complexity-indicator-component) to learn more.
+
+> **Note**: Currently, this feature is only available for the Angular UI, but we will be implemented for other UIs in the next version.
+
+## Community News
+
+### DevNot Developer Summit 2023
+
+
+
+We are thrilled to announce that the co-founder of [Volosoft](https://volosoft.com/) and Lead Developer of the ABP Framework, Halil Ibrahim Kalkan will give a speech about "Building a Kubernetes Integrated Local Development Environment" in the [Developer Summit 2023 event](https://summit.devnot.com/) on the 7th of October.
+
+### New ABP Community Posts
+
+There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
+
+* [ABP Commercial - GDPR Module Overview](https://community.abp.io/posts/abp-commercial-gdpr-module-overview-kvmsm3ku) by [Engincan Veske](https://twitter.com/EngincanVeske)
+* [Video: ABP Framework Data Transfer Objects](https://community.abp.io/videos/abp-framework-data-transfer-objects-qwebfqz5) by [Hamza Albreem](https://github.com/braim23)
+* [Video: ABP Framework Essentials: MongoDB](https://community.abp.io/videos/abp-framework-essentials-mongodb-gwlblh5x) by [Hamza Albreem](https://github.com/braim23)
+* [ABP Modules and Entity Dependencies](https://community.abp.io/posts/abp-modules-and-entity-dependencies-hn7wr093) by [Jack Fistelmann](https://github.com/nebula2)
+* [How to add dark mode support to the Basic Theme in 3 steps?](https://community.abp.io/posts/how-to-add-dark-mode-support-to-the-basic-theme-in-3-steps-ge9c0f85) by [Enis Necipoğlu](https://twitter.com/EnisNecipoglu)
+* [Deploying docker image to Azure with yml and bicep through Github Actions](https://community.abp.io/posts/deploying-docker-image-to-azure-with-yml-and-bicep-through-github-actions-cjiuh55m) by [Sturla](https://community.abp.io/members/Sturla)
+
+Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community.
+
+## Conclusion
+
+This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.4/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.4 RC and provide feedback to help us release a more stable version.
+
+Thanks for being a part of this community!
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cs.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cs.png
new file mode 100644
index 0000000000..1179118621
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cs.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cshtml.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cshtml.png
new file mode 100644
index 0000000000..0ed90a33de
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/book-extended-cshtml.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/cover-image.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/cover-image.png
new file mode 100644
index 0000000000..06ced79397
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/cover-image.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/developersummit.jpg b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/developersummit.jpg
new file mode 100644
index 0000000000..65bae90315
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/developersummit.jpg differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/editions.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/editions.png
new file mode 100644
index 0000000000..331706daa7
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/editions.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/error-page.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/error-page.png
new file mode 100644
index 0000000000..8ece260ec3
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/error-page.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/fluid-layout.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/fluid-layout.png
new file mode 100644
index 0000000000..6150f3e2bd
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/fluid-layout.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/maui.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/maui.png
new file mode 100644
index 0000000000..4205001d80
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/maui.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/mobile-toolbars.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/mobile-toolbars.png
new file mode 100644
index 0000000000..6dcde5da60
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/mobile-toolbars.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-all-tenants.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-all-tenants.png
new file mode 100644
index 0000000000..b893360447
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-all-tenants.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-tenants.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-tenants.png
new file mode 100644
index 0000000000..fb6b8364fb
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/move-tenants.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/page-feedback.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/page-feedback.png
new file mode 100644
index 0000000000..dbdd9fe199
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/page-feedback.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/password-complexity.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/password-complexity.png
new file mode 100644
index 0000000000..55bc8a8f43
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/password-complexity.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/settings.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/settings.png
new file mode 100644
index 0000000000..1e835661a0
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/settings.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-backend.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-backend.png
new file mode 100644
index 0000000000..cb14dfc0f1
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-backend.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-ui.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-ui.png
new file mode 100644
index 0000000000..132988a873
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code-ui.png differ
diff --git a/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code.png b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code.png
new file mode 100644
index 0000000000..2e510d0768
Binary files /dev/null and b/docs/en/Blog-Posts/2023-08-15 v7_4_Preview/suite-custom-code.png differ
diff --git a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md
index 6abf984298..ae688d6981 100644
--- a/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md
+++ b/docs/en/Community-Articles/2021-10-31-Many-to-Many-Relationship-with-ABP-and-EF-Core/POST.md
@@ -594,7 +594,7 @@ namespace BookStore.EntityFrameworkCore
/* Configure your own tables/entities inside here */
builder.Entity(b =>
{
- b.ToTable(BookStoreConsts.DbTablePrefix + "Authors" + BookStoreConsts.DbSchema);
+ b.ToTable(BookStoreConsts.DbTablePrefix + "Authors", BookStoreConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name)
@@ -608,7 +608,7 @@ namespace BookStore.EntityFrameworkCore
builder.Entity(b =>
{
- b.ToTable(BookStoreConsts.DbTablePrefix + "Books" + BookStoreConsts.DbSchema);
+ b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name)
@@ -624,7 +624,7 @@ namespace BookStore.EntityFrameworkCore
builder.Entity(b =>
{
- b.ToTable(BookStoreConsts.DbTablePrefix + "Categories" + BookStoreConsts.DbSchema);
+ b.ToTable(BookStoreConsts.DbTablePrefix + "Categories", BookStoreConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name)
@@ -634,7 +634,7 @@ namespace BookStore.EntityFrameworkCore
builder.Entity(b =>
{
- b.ToTable(BookStoreConsts.DbTablePrefix + "BookCategories" + BookStoreConsts.DbSchema);
+ b.ToTable(BookStoreConsts.DbTablePrefix + "BookCategories", BookStoreConsts.DbSchema);
b.ConfigureByConvention();
//define composite key
diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/README.md b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/README.md
new file mode 100644
index 0000000000..2db041436c
--- /dev/null
+++ b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/README.md
@@ -0,0 +1,450 @@
+# Cascading Option Loading with Extensions System in ABP Angular
+
+This article will show how to load cascading options with an extensions system in ABP Angular. For this example, we'll simulate renting a book process. Besides our default form properties, we'll contribute `Name` property to our `Rent Form Modal` in the Books module. This property will be loaded after `Genre` is selected.
+
+> Before starting this article, I suggest you read the [ABP Angular Dynamic Form Extensions](https://docs.abp.io/en/abp/latest/UI/Angular/Dynamic-Form-Extensions)
+
+### Environment
+
+- **ABP Framework Version:** ~7.3.0 (`~` means that use the latest patch version of the specified release)
+- **DB Provider:** MongoDB
+- **Angular Version:** ~16.0.0
+
+### Project structure
+
+The books module is not a library; for this demo, it'll placed in the application itself.
+
+
+
+- **books folder:** Contains default form properties, tokens, models, etc. It's similar to the ABP module structure.
+- Also I've used **standalone** and **signals** feature in this demo.
+- **books-extended folder:** Contains only `Name` property for the contribute `Rent Form Modal` inside the Books module.
+- **For more readability, I've used TS path aliases in this demo. Don't forget to export files in `index.ts` file 🙂**
+
+
+
+### First look at the demo
+
+
+
+### What is the Extension system?
+
+
+
+# Reviewing the code step by step
+
+**1. Create default form properties for `Rent Form` in the `Books` module**
+
+- `getInjected` function is the key point of the cascading loading
+- We can reach and track any value from `Service` or `Component`
+- In that way we can load options according to the selected value
+
+```ts
+// ~/books/defaults/default-books-form.props.ts
+
+import { Validators } from "@angular/forms";
+import { map, of } from "rxjs";
+import { ePropType, FormProp } from "@abp/ng.theme.shared/extensions";
+import { BookDto, AuthorService, BooksService } from "../proxy";
+import { RentBookComponent } from "../components";
+import { DefaultOption } from "../utils";
+
+const { required } = Validators;
+
+export const DEFAULT_RENT_FORM_PROPS = FormProp.createMany([
+ {
+ type: ePropType.String,
+ id: "authorId",
+ name: "authorId",
+ displayName: "BookStore::Author",
+ defaultValue: null,
+ validators: () => [required],
+ options: (data) => {
+ const { authors } = data.getInjected(AuthorService);
+
+ return of([
+ DefaultOption,
+ ...authors().map((author) => ({ value: author.id, key: author.name })),
+ ]);
+ },
+ },
+ {
+ type: ePropType.String,
+ id: "genreId",
+ name: "genreId",
+ displayName: "BookStore::Genre",
+ defaultValue: null,
+ validators: () => [required],
+ options: (data) => {
+ const rentBookComponent = data.getInjected(RentBookComponent);
+ const { genres } = data.getInjected(BooksService);
+
+ const genreOptions = genres().map(({ id, name }) => ({
+ value: id,
+ key: name,
+ }));
+
+ return rentBookComponent.form.controls.authorId.valueChanges.pipe(
+ map((value: string | undefined) =>
+ value ? [DefaultOption, ...genreOptions] : [DefaultOption]
+ )
+ );
+ },
+ },
+ {
+ type: ePropType.Date,
+ id: "returnDate",
+ name: "returnDate",
+ displayName: "BookStore::ReturnDate",
+ defaultValue: null,
+ validators: () => [required],
+ },
+]);
+```
+
+**2. Configure tokens and config options**
+
+The documentation explains these steps; that's why I won't explain it again. If documents or samples are not enough, please let me know in the comments 🙂
+
+**Extensions Token**
+
+```ts
+// ~/books/tokens/extensions.token.ts
+
+import { CreateFormPropContributorCallback } from "@abp/ng.theme.shared/extensions";
+import { InjectionToken } from "@angular/core";
+import { BookDto } from "../proxy";
+import { eBooksComponents } from "../enums";
+import { DEFAULT_RENT_FORM_PROPS } from "../defaults";
+
+export const DEFAULT_BOOK_STORE_CREATE_FORM_PROPS = {
+ [eBooksComponents.RentBook]: DEFAULT_RENT_FORM_PROPS,
+};
+
+export const BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS =
+ new InjectionToken(
+ "BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS"
+ );
+
+type CreateFormPropContributors = Partial<{
+ [eBooksComponents.RentBook]: CreateFormPropContributorCallback[];
+ /**
+ * Other creation form prop contributors...
+ */
+ // [eBooksComponents.CreateBook]: CreateFormPropContributorCallback[];
+}>;
+```
+
+**Extensions Config Option**
+
+```ts
+// ~/books/models/config-options.ts
+
+import { CreateFormPropContributorCallback } from "@abp/ng.theme.shared/extensions";
+import { BookDto } from "../proxy";
+import { eBooksComponents } from "../enums";
+
+export type BookStoreRentFormPropContributors = Partial<{
+ [eBooksComponents.RentBook]: CreateFormPropContributorCallback[];
+}>;
+
+export interface BooksConfigOptions {
+ rentFormPropContributors?: BookStoreRentFormPropContributors;
+}
+```
+
+**3. Extensions Guard**
+
+It'll to collect all contributors from [ExtensionsService](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/extensions/src/lib/services/extensions.service.ts)
+
+```ts
+// ~/books/guards/extensions.guard.ts
+
+import { Injectable, inject } from "@angular/core";
+import { Observable, map, tap } from "rxjs";
+import { ConfigStateService, IAbpGuard } from "@abp/ng.core";
+import {
+ ExtensionsService,
+ getObjectExtensionEntitiesFromStore,
+ mapEntitiesToContributors,
+ mergeWithDefaultProps,
+} from "@abp/ng.theme.shared/extensions";
+import {
+ BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS,
+ DEFAULT_BOOK_STORE_CREATE_FORM_PROPS,
+} from "../tokens";
+
+@Injectable()
+export class BooksExtensionsGuard implements IAbpGuard {
+ protected readonly configState = inject(ConfigStateService);
+ protected readonly extensions = inject(ExtensionsService);
+
+ canActivate(): Observable {
+ const createFormContributors =
+ inject(BOOK_STORE_RENT_FORM_PROP_CONTRIBUTORS, { optional: true }) || {};
+
+ return getObjectExtensionEntitiesFromStore(
+ this.configState,
+ "BookStore"
+ ).pipe(
+ mapEntitiesToContributors(this.configState, "BookStore"),
+ tap((objectExtensionContributors) => {
+ mergeWithDefaultProps(
+ this.extensions.createFormProps,
+ DEFAULT_BOOK_STORE_CREATE_FORM_PROPS,
+ objectExtensionContributors.createForm,
+ createFormContributors
+ );
+ }),
+ map(() => true)
+ );
+ }
+}
+```
+
+Yes, I'm still using class-based guard 🙂 much more flexible...
+
+**4. RentBookComponent**
+
+- Our trackable variable is defined here `(form:FormGroup)`, which means We'll track this variable in `options` property at defaults || contributors files.
+- Providing `AuthorService`, also `EXTENSIONS_IDENTIFIER` for the reach dynamic properties
+
+```ts
+import {
+ ChangeDetectionStrategy,
+ Component,
+ EventEmitter,
+ Injector,
+ Output,
+ inject,
+} from "@angular/core";
+import { FormGroup } from "@angular/forms";
+import { CoreModule, uuid } from "@abp/ng.core";
+import { ThemeSharedModule } from "@abp/ng.theme.shared";
+import {
+ EXTENSIONS_IDENTIFIER,
+ FormPropData,
+ UiExtensionsModule,
+ generateFormFromProps,
+} from "@abp/ng.theme.shared/extensions";
+import { AuthorService, BookDto, BooksService } from "../../proxy";
+import { eBooksComponents } from "../../enums";
+
+@Component({
+ standalone: true,
+ selector: "app-rent-book",
+ templateUrl: "./rent-book.component.html",
+ imports: [CoreModule, UiExtensionsModule, ThemeSharedModule],
+ providers: [
+ {
+ provide: EXTENSIONS_IDENTIFIER,
+ useValue: eBooksComponents.RentBook,
+ },
+ AuthorService,
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class RentBookComponent {
+ protected readonly injector = inject(Injector);
+ protected readonly authorService = inject(AuthorService);
+ protected readonly booksService = inject(BooksService);
+
+ //#region Just for demo
+ readonly #authors = this.authorService.authors();
+ readonly #genres = this.booksService.genres();
+ readonly #books = this.booksService.books();
+ //#endregion
+
+ protected modalVisible = true;
+ @Output() modalVisibleChange = new EventEmitter();
+
+ selected: BookDto;
+ form: FormGroup;
+ modalBusy = false;
+
+ protected buildForm(): void {
+ const data = new FormPropData(this.injector, this.selected);
+ this.form = generateFormFromProps(data);
+ }
+
+ constructor() {
+ this.buildForm();
+ }
+
+ save(): void {
+ if (this.form.invalid) {
+ return;
+ }
+
+ this.modalBusy = true;
+
+ const { authorId, genreId, bookId, returnDate } = this.form.value;
+
+ //#region Just for demo
+ const authorName = this.#authors.find(({ id }) => id === authorId).name;
+ const genreName = this.#genres.find(({ id }) => id === genreId).name;
+ const bookName = this.#books.find(({ id }) => id === bookId).name;
+ //#endregion
+
+ this.booksService.rentedBooks.update((books) => [
+ {
+ id: uuid(),
+ name: bookName,
+ author: authorName,
+ genre: genreName,
+ returnDate,
+ },
+ ...books,
+ ]);
+
+ this.modalBusy = false;
+ this.modalVisible = false;
+ }
+}
+```
+
+```html
+
+
+
{{ 'BookStore::RentABook' | abpLocalization }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'AbpIdentity::Save' | abpLocalization }}
+
+
+
+```
+
+Up to now, we have constructed our module's default form properties.
+
+- As you can see, there are no book names we'll add them via contributors
+
+
+
+## Next, add new property dynamically (book name list as dropdown)
+
+- Created new folder ./src/app/books-extended
+- Create contributors/form-prop.contributors.ts
+
+```ts
+// ~/books-extened/contributors/form-prop.contributors.ts
+
+import { Validators } from "@angular/forms";
+import { map } from "rxjs";
+import {
+ ePropType,
+ FormProp,
+ FormPropList,
+} from "@abp/ng.theme.shared/extensions";
+import {
+ BookDto,
+ BookStoreRentFormPropContributors,
+ BooksService,
+ DefaultOption,
+ RentBookComponent,
+ eBooksComponents,
+} from "@book-store/books";
+
+const { required, maxLength } = Validators;
+
+const bookIdProp = new FormProp({
+ type: ePropType.String,
+ id: "bookId",
+ name: "bookId",
+ displayName: "BookStore::Name",
+ options: (data) => {
+ const rentBook = data.getInjected(RentBookComponent);
+ const { books } = data.getInjected(BooksService);
+ const bookOptions = books().map(({ id, name }) => ({
+ value: id,
+ key: name,
+ }));
+
+ return rentBook.form.controls.genreId.valueChanges.pipe(
+ map((value: string | undefined) =>
+ value ? [DefaultOption, ...bookOptions] : [DefaultOption]
+ )
+ );
+ },
+ validators: () => [required, maxLength(255)],
+});
+
+export function bookIdPropContributor(propList: FormPropList) {
+ propList.addByIndex(bookIdProp, 2);
+}
+
+export const bookStoreRentFormPropContributors: BookStoreRentFormPropContributors =
+ {
+ [eBooksComponents.RentBook]: [bookIdPropContributor],
+ };
+```
+
+- Load new contributions via routing & forLazy method
+
+```ts
+// ~/app-routing.module.ts
+import { bookStoreRentFormPropContributors } from "./books-extended/contributors/form-prop.contributors";
+
+const routes: Routes = [
+ // other routes...
+ {
+ path: "books",
+ loadChildren: () =>
+ import("@book-store/books").then((m) =>
+ m.BooksModule.forLazy({
+ rentFormPropContributors: bookStoreRentFormPropContributors,
+ })
+ ),
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forRoot(routes, {})],
+ exports: [RouterModule],
+})
+export class AppRoutingModule {}
+```
+
+Finally, we've added a new property to our module, and it'll be loaded after `Genre` is selected.
+
+## Conclusion
+
+
+
+- In ABP Angular, we can create form properties and load dropdown options dynamically via the Extensions System
+- We can reach and track any value from `Service` or `Component`
+- We can create our custom library or module and contribute it to any module in the application
+
+Thanks for reading, I hope it was helpful. If you have any questions, please let me know in the comments section. 👋👋
+
+> You can find the source code of this article on [Github](https://github.com/abpframework/abp-samples/tree/master/AngularCascadingOptionLoading/Volo.BookStore)
diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/gif/cascading-loading-demo.gif b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/gif/cascading-loading-demo.gif
new file mode 100644
index 0000000000..ae69070f28
Binary files /dev/null and b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/gif/cascading-loading-demo.gif differ
diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/extensions-system-document.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/extensions-system-document.png
new file mode 100644
index 0000000000..7f806ec42d
Binary files /dev/null and b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/extensions-system-document.png differ
diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/folder-structure.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/folder-structure.png
new file mode 100644
index 0000000000..2d3a828773
Binary files /dev/null and b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/folder-structure.png differ
diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/rent-form-without-contribution.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/rent-form-without-contribution.png
new file mode 100644
index 0000000000..e29d525f7b
Binary files /dev/null and b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/rent-form-without-contribution.png differ
diff --git a/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/ts-config-file.png b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/ts-config-file.png
new file mode 100644
index 0000000000..6b06e8ca90
Binary files /dev/null and b/docs/en/Community-Articles/2023-07-09-Cascading-Option-Loading-With-Extensions/assets/img/ts-config-file.png differ
diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/POST.md b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/POST.md
new file mode 100644
index 0000000000..a68bc106a5
--- /dev/null
+++ b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/POST.md
@@ -0,0 +1,236 @@
+# Moving Background Job Execution To A Separate Application
+
+In this article, I will show you how to move the background job execution to a separate application.
+
+Here are some benefits of doing this:
+
+* If your background jobs consume high system resources (CPU, RAM or Disk), then you can deploy that background application to a dedicated server so it won't affect your application's performance.
+* You can scale your background job application independently from your web application. For example, you can deploy multiple instances of your background job application to a Kubernetes cluster and scale it easily.
+
+Here are some disadvantages of doing this:
+
+* You need to deploy and maintain at least two applications instead of one.
+* You need to implement a mechanism to share the common code between your applications. For example, you can create a shared project and add it to your applications as a project reference.
+
+## Source code
+
+You can find the source code of the application at [abpframework/abp-samples](https://github.com/abpframework/abp-samples/tree/master/SeparateBackgroundJob).
+
+You can check the PR to see the changes step by step: [abpframework/abp-samples#250](https://github.com/abpframework/abp-samples/pull/250)
+
+## Creating the Web Application
+
+First, we need to create a new web application using the ABP CLI:
+
+```bash
+abp new SeparateBackgroundJob -t app
+```
+
+* Create a shared project named `SeparateBackgroundJob.Common.Shared` to share the `BackgroundJob` and `BackgroundJobArgs` classes between the web and job executor applications.
+* Install the `Volo.Abp.BackgroundJobs.Abstractions` package to the `SeparateBackgroundJob.Common.Shared` project.
+
+Add the `SeparateBackgroundJobCommonSharedModule` class to the `SeparateBackgroundJob.Common.Shared` project:
+
+```csharp
+[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))]
+public class SeparateBackgroundJobCommonSharedModule : AbpModule
+{
+}
+```
+
+Add the `MyReportJob` and `MyReportJobArgs` classes to the `SeparateBackgroundJob.Common.Shared` project:
+
+```csharp
+public class MyReportJob : AsyncBackgroundJob, ITransientDependency
+{
+ public override Task ExecuteAsync(MyReportJobArgs args)
+ {
+ Logger.LogInformation("Executing MyReportJob with args: {0}", args.Content);
+ return Task.CompletedTask;
+ }
+}
+
+public class MyReportJobArgs
+{
+ public string? Content { get; set; }
+}
+```
+
+Add the `SeparateBackgroundJob.Common.Shared` project reference to the `SeparateBackgroundJob.Domain` project and add `SeparateBackgroundJobCommonSharedModule` to the `DependsOn` attribute of the `SeparateBackgroundJobDomainModule` class:
+
+```csharp
+[DependsOn(
+ typeof(SeparateBackgroundJobDomainSharedModule),
+ typeof(AbpAuditLoggingDomainModule),
+ typeof(AbpBackgroundJobsDomainModule),
+ typeof(AbpFeatureManagementDomainModule),
+ typeof(AbpIdentityDomainModule),
+ typeof(AbpOpenIddictDomainModule),
+ typeof(AbpPermissionManagementDomainOpenIddictModule),
+ typeof(AbpPermissionManagementDomainIdentityModule),
+ typeof(AbpSettingManagementDomainModule),
+ typeof(AbpTenantManagementDomainModule),
+ typeof(AbpEmailingModule),
+ typeof(SeparateBackgroundJobCommonSharedModule) //Add this line
+)]
+public class SeparateBackgroundJobDomainModule : AbpModule
+```
+
+Open the `Index.cshtml` and replace the content with the following code:
+
+```csharp
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using SeparateBackgroundJob.Localization
+@using Volo.Abp.Users
+@model SeparateBackgroundJob.Web.Pages.IndexModel
+@inject IHtmlLocalizer L
+@inject ICurrentUser CurrentUser
+
+@section styles {
+
+}
+
+@section scripts {
+
+}
+
+
+
+
+
+ Add NEW BACKGROUND JOB
+
+
+
+
+
+
+
+```
+
+Open the `Index.cshtml.cs` and replace the content with the following code:
+
+```csharp
+public class IndexModel : SeparateBackgroundJobPageModel
+{
+ private readonly IBackgroundJobManager _backgroundJobManager;
+
+ [BindProperty(SupportsGet = true)]
+ public string? ReportContent { get; set; }
+
+ public IndexModel(IBackgroundJobManager backgroundJobManager)
+ {
+ _backgroundJobManager = backgroundJobManager;
+ }
+
+ public void OnGet()
+ {
+
+ }
+
+ public async Task OnPostAsync()
+ {
+ await _backgroundJobManager.EnqueueAsync(new MyReportJobArgs
+ {
+ Content = ReportContent
+ });
+
+ Alerts.Success("Job is queued!");
+ }
+}
+```
+
+Run the application and navigate to the home page. You should see the following page:
+
+
+
+When you enter some text and click the **Add** button, the job will be queued and executed in the web application:
+
+## Creating the Console Application
+
+Now we split the background job execution to a separate console application.
+
+Open the `SeparateBackgroundJobWebModule` class to disable the background job execution in the web application:
+
+```csharp
+public class SeparateBackgroundJobWebModule : AbpModule
+{
+ ....
+
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ ...
+
+ //Disable background job execution in the web application
+ Configure(options =>
+ {
+ options.IsJobExecutionEnabled = false;
+ });
+ }
+
+ ...
+}
+```
+
+* Create a new console application using the ABP CLI:
+
+```bash
+abp new BackgroundJobExecutor -t console
+```
+
+* Add the `BackgroundJobExecutor` project to the solution of the web application.
+* Add the `SeparateBackgroundJob.Common.Shared` project reference to the `BackgroundJobExecutor` project.
+* Install the `Volo.Abp.BackgroundJobs.EntityFrameworkCore` and `Volo.Abp.EntityFrameworkCore.SqlServer` packages to the `BackgroundJobExecutor` project.
+
+Update the `BackgroundJobExecutorModule` class as follows:
+
+```csharp
+[DependsOn(
+ typeof(AbpAutofacModule),
+ typeof(AbpBackgroundJobsEntityFrameworkCoreModule),
+ typeof(AbpEntityFrameworkCoreSqlServerModule),
+ typeof(SeparateBackgroundJobCommonSharedModule)
+)]
+public class BackgroundJobExecutorModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.UseSqlServer();
+ });
+ }
+
+ ....
+}
+```
+
+Open the `appsettings.json` file to configure the [connection string](https://docs.abp.io/en/abp/latest/Connection-Strings#configure-the-connection-strings):
+
+```json
+{
+ "ConnectionStrings": {
+ "AbpBackgroundJobs": "Server=(LocalDb)\\MSSQLLocalDB;Database=SeparateBackgroundJob;Trusted_Connection=True"
+ }
+}
+```
+
+> You must use the same connection string for the web application, `AbpBackgroundJobs` is the default connection string name for the background job module.
+
+The solution structure should look like this:
+
+
+
+Now, run the web and console application. When you enter some text and click the **Add** button, the job will be queued and executed in the console application:
+
+
diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/1.png b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/1.png
new file mode 100644
index 0000000000..37089353eb
Binary files /dev/null and b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/1.png differ
diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/2.png b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/2.png
new file mode 100644
index 0000000000..0e56eee637
Binary files /dev/null and b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/2.png differ
diff --git a/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/solution.png b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/solution.png
new file mode 100644
index 0000000000..ab7abe9a89
Binary files /dev/null and b/docs/en/Community-Articles/2023-09-20-Moving-Background-Job-Execution-To-A-Separate-Application/images/solution.png differ
diff --git a/docs/en/Emailing.md b/docs/en/Emailing.md
index bfb3058862..48635b2565 100644
--- a/docs/en/Emailing.md
+++ b/docs/en/Emailing.md
@@ -58,7 +58,11 @@ namespace MyProject
`SendAsync` method has overloads to supply more parameters like;
* **from**: You can set this as the first argument to set a sender email address. If not provided, the default sender address is used (see the email settings below).
+* **to**: You can set the target email address.
+* **subject**: You can set the email subject.
+* **body**: You can set the email body.
* **isBodyHtml**: Indicates whether the email body may contain HTML tags. **Default: true**.
+* **additionalEmailSendingArgs**: This parameter is used to pass additional arguments to the `IEmailSender` implementation. Include: CC(Carbon copy), a list of `EmailAttachment` and an extra properties.
> `IEmailSender` is the suggested way to send emails, since it makes your code provider independent.
diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md
index 716038d412..9ac7e76f24 100644
--- a/docs/en/Entity-Framework-Core-Migrations.md
+++ b/docs/en/Entity-Framework-Core-Migrations.md
@@ -173,7 +173,7 @@ First step is to change the connection string section inside all the `appsetting
````json
"ConnectionStrings": {
- "Default": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
+ "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
}
````
@@ -184,7 +184,7 @@ Change it as shown below:
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True",
"AbpPermissionManagement": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True",
"AbpSettingManagement": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True",
- "AbpAuditLogging": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True"
+ "AbpAuditLogging": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True"
}
````
diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md
index e2cfd21a05..cb9393b1aa 100644
--- a/docs/en/Entity-Framework-Core.md
+++ b/docs/en/Entity-Framework-Core.md
@@ -594,6 +594,18 @@ Whenever you access to a property/collection, EF Core automatically performs an
See also [lazy loading document](https://docs.microsoft.com/en-us/ef/core/querying/related-data/lazy) of the EF Core.
+## Read-Only Repositories
+
+ABP Framework provides read-only [repository](Repositories.md) interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`) to explicitly indicate that your purpose is to query data, but not change it. If so, you can inject these interfaces into your services.
+
+Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method on the LINQ expression, or `EnableTracking()` extension method on the repository object (See *Enabling / Disabling the Change Tracking* section in this document).
+
+> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface.
+
+## Enabling / Disabling the Change Tracking
+
+In addition to the read-only repositories, ABP allows to manually control the change tracking behavior for querying objects. Please see the *Enabling / Disabling the Change Tracking* section of the [Repositories documentation](Repositories.md) to learn how to use it.
+
## Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example:
diff --git a/docs/en/Getting-Started-Running-Solution-Single-Layer.md b/docs/en/Getting-Started-Running-Solution-Single-Layer.md
index cef80c7807..f18f3119f9 100644
--- a/docs/en/Getting-Started-Running-Solution-Single-Layer.md
+++ b/docs/en/Getting-Started-Running-Solution-Single-Layer.md
@@ -20,7 +20,7 @@ Check the **connection string** in the `appsettings.json` file under the `YourPr
````json
"ConnectionStrings": {
- "Default": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
+ "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
}
````
diff --git a/docs/en/Getting-Started-Running-Solution.md b/docs/en/Getting-Started-Running-Solution.md
index 36cd5ffe0a..f3adcac67d 100644
--- a/docs/en/Getting-Started-Running-Solution.md
+++ b/docs/en/Getting-Started-Running-Solution.md
@@ -21,7 +21,7 @@ Check the **connection string** in the `appsettings.json` file under the {{if Ti
````json
"ConnectionStrings": {
- "Default": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
+ "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
}
````
diff --git a/docs/en/Object-To-Object-Mapping.md b/docs/en/Object-To-Object-Mapping.md
index c12b260921..40efdd7ee6 100644
--- a/docs/en/Object-To-Object-Mapping.md
+++ b/docs/en/Object-To-Object-Mapping.md
@@ -320,9 +320,22 @@ public class MyCustomUserMapper : IObjectMapper, ITransientDepend
}
````
-ABP automatically discovers and registers the `MyCustomUserMapper` and it is automatically used whenever you use the `IObjectMapper` to map `User` to `UserDto`.
-
-A single class may implement more than one `IObjectMapper` each for a different object pairs.
+ABP automatically discovers and registers the `MyCustomUserMapper` and it is automatically used whenever you use the `IObjectMapper` to map `User` to `UserDto`. A single class may implement more than one `IObjectMapper` each for a different object pairs.
> This approach is powerful since `MyCustomUserMapper` can inject any other service and use in the `Map` methods.
+Once you implement `IObjectMapper`, ABP can automatically convert a collection of `User` objects to a collection of `UserDto` objects. The following generic collection types are supported:
+
+* `IEnumerable`
+* `ICollection`
+* `Collection`
+* `IList`
+* `List`
+* `T[]` (array)
+
+**Example:**
+
+````csharp
+var users = await _userRepository.GetListAsync(); // returns List
+var dtos = ObjectMapper.Map, List>(users); // creates List
+````
diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md
index 95f908d004..03fb27ab50 100644
--- a/docs/en/Repositories.md
+++ b/docs/en/Repositories.md
@@ -176,6 +176,77 @@ Some features (like soft-delete, multi-tenancy and audit logging) won't work, so
The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`.
+### Enabling / Disabling the Change Tracking
+
+ABP provides repository extension methods and attributes those can be used to control the change tracking behavior for queried entities in the underlying database provider.
+
+Disabling change tracking can gain performance if you query many entities from the database for read-only purposes. Querying single or a few entities won't make much performance difference, but you are free to use it whenever you like.
+
+> If the underlying database provider doesn't support change tracking, then this system won't have any effect. [Entity Framework Core](Entity-Framework-Core.md) supports change tracking, for example, while the [MongoDB](MongoDB.md) provider doesn't support it.
+
+#### Repository Extension Methods for Change Tracking
+
+Change tracking is enabled unless you explicitly disable it.
+
+**Example: Using the `DisableTracking` extension method**
+
+````csharp
+public class MyDemoService : ApplicationService
+{
+ private readonly IRepository _personRepository;
+
+ public MyDemoService(IRepository personRepository)
+ {
+ _personRepository = personRepository;
+ }
+
+ public async Task DoItAsync()
+ {
+ // Change tracking is enabled in that point (by default)
+
+ using (_personRepository.DisableTracking())
+ {
+ // Change tracking is disabled in that point
+ var list = await _personRepository.GetPagedListAsync(0, 100, "Name ASC");
+ }
+
+ // Change tracking is enabled in that point (by default)
+ }
+}
+````
+
+> `DisableTracking` extension method returns a `IDisposable` object, so you can safely **restore** the change tracking behavior to the **previous state** one the `using` block ends. Basically, `DisableTracking` method ensures that the change tracking is disabled inside the `using` block, but doesn't affect outside of the `using` block. That means, if change tracking was already disabled, `DisableTracking` and the disposable return value do nothing.
+
+`EnableTracking()` method works exactly opposite to the `DisableTracking()` method. You typically won't use it (because the change tracking is already enabled by default), but it is there in case of you need that.
+
+#### Attributes for Change Tracking
+
+You typically use the `DisableTracking()` method for the application service methods those only returns data, but doesn't make any change on entities. For such cases, you can use the `DisableEntityChangeTracking` attribute on your method/class as a shortcut to disable the change tracking for whole method body.
+
+**Example: Using the `DisableEntityChangeTracking` attribute on a method**
+
+````csharp
+[DisableEntityChangeTracking]
+public virtual async Task> GetListAsync()
+{
+ /* We disabled the change tracking in this method
+ because we won't change the people objects */
+ var people = await _personRepository.GetListAsync();
+ return ObjectMapper.Map, List(people);
+}
+````
+
+`EnableEntityChangeTracking` can be used for the opposite purpose, and it ensures that the change tracking is enabled for a given method. Since the change tracking is enabled by default, `EnableEntityChangeTracking` may be needed only if you know that your method is called from a context that disables the change tracking.
+
+`DisableEntityChangeTracking` and `EnableEntityChangeTracking` attributes can be used on a **method** or on a **class** (which affects all of the class methods).
+
+ABP uses dynamic proxying to make these attributes working. There are some rules here:
+
+* If you are **not injecting** the service over an interface (like `IPersonAppService`), then the methods of the service must be `virtual`. Otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work.
+* Only `async` methods (methods returning a `Task` or `Task`) are intercepted.
+
+> Change tracking behavior doesn't affect tracking entity objects returned from `InsertAsync` and `UpdateAsync` methods. The objects returned from these methods are always tracked (if the underlying provider has the change tracking feature) and any change you made to these objects are saved into the database.
+
## Other Generic Repository Types
Standard `IRepository` interface exposes the standard `IQueryable` and you can freely query using the standard LINQ methods. This is fine for most of the applications. However, some ORM providers or database systems may not support standard `IQueryable` interface. If you want to use such providers, you can't rely on the `IQueryable`.
@@ -205,8 +276,6 @@ Methods:
- `WithDetails()` 1 overload
- `WithDetailsAsync()` 1 overload
-
-
Where as the `IReadOnlyBasicRepository` provides the following methods:
- `GetCountAsync()`
@@ -217,6 +286,12 @@ They can all be seen as below:

+#### Read Only Repositories behavior in Entity Framework Core
+
+Entity Framework Core read-only repository implementation uses [EF Core's No-Tracking feature](https://learn.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries). That means the entities returned from the repository will not be tracked by the EF Core [change tracker](https://learn.microsoft.com/en-us/ef/core/change-tracking/), because it is expected that you won't update entities queried from a read-only repository. If you need to track the entities, you can still use the [AsTracking()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.astracking) extension method on the LINQ expression, or `EnableTracking()` extension method on the repository object (See *Enabling / Disabling the Change Tracking* section in this document).
+
+> This behavior works only if the repository object is injected with one of the read-only repository interfaces (`IReadOnlyRepository<...>` or `IReadOnlyBasicRepository<...>`). It won't work if you have injected a standard repository (e.g. `IRepository<...>`) then casted it to a read-only repository interface.
+
### Generic Repository without a Primary Key
If your entity does not have an Id primary key (it may have a composite primary key for instance) then you cannot use the `IRepository` (or basic/readonly versions) defined above. In that case, you can inject and use `IRepository` for your entity.
diff --git a/docs/en/UI/AspNetCore/Testing.md b/docs/en/UI/AspNetCore/Testing.md
index 2c880594d3..6efc967cb0 100644
--- a/docs/en/UI/AspNetCore/Testing.md
+++ b/docs/en/UI/AspNetCore/Testing.md
@@ -198,23 +198,10 @@ ABP Framework doesn't provide any infrastructure to test your JavaScript code. Y
> Volo.Abp.AspNetCore.TestBase package is already installed in the `.Web.Tests` project.
-This package provides the `AbpAspNetCoreIntegratedTestBase` as the fundamental base class to derive the test classes from. The `MyProjectWebTestBase` base class used above inherits from the `AbpAspNetCoreIntegratedTestBase`, so we indirectly inherited the `AbpAspNetCoreIntegratedTestBase`.
+This package provides the `AbpWebApplicationFactoryIntegratedTest` as the fundamental base class to derive the test classes from. It's inherited from the [WebApplicationFactory](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests) class provided by the ASP.NET Core.
-### Base Properties
-
-The `AbpAspNetCoreIntegratedTestBase` provides the following base properties those are used in the tests:
-
-* `Server`: A `TestServer` instance that hosts the web application in tests.
-* `Client`: An `HttpClient` instance that is configured to perform requests to the test server.
-* `ServiceProvider`: The service provider that you can resolve services in case of need.
-
-### Base Methods
-
-`AbpAspNetCoreIntegratedTestBase` provides the following methods that you can override if you need to customize the test server:
-
-* `ConfigureServices` can be overridden to register/replace services only for the derived test class.
-* `CreateHostBuilder` can be used to customize building the `IHostBuilder`.
+The `MyProjectWebTestBase` base class used above inherits from the `AbpWebApplicationFactoryIntegratedTest`, so we indirectly inherited the `AbpWebApplicationFactoryIntegratedTest`.
See Also
-
+* [Integration tests in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests)
* [Overall / Server Side Testing](../../Testing.md)
\ No newline at end of file
diff --git a/docs/zh-Hans/Getting-Started-Running-Solution.md b/docs/zh-Hans/Getting-Started-Running-Solution.md
index 8ecd85ec58..f0a4942d64 100644
--- a/docs/zh-Hans/Getting-Started-Running-Solution.md
+++ b/docs/zh-Hans/Getting-Started-Running-Solution.md
@@ -21,7 +21,7 @@
````json
"ConnectionStrings": {
- "Default": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
+ "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True"
}
````
diff --git a/docs/zh-Hans/UI/AspNetCore/Testing.md b/docs/zh-Hans/UI/AspNetCore/Testing.md
index e5b09353fb..1dfdbe367e 100644
--- a/docs/zh-Hans/UI/AspNetCore/Testing.md
+++ b/docs/zh-Hans/UI/AspNetCore/Testing.md
@@ -198,23 +198,10 @@ ABP框架不提供任何基础设施来测试JavaScript代码. 你可以使用
> Volo.Abp.AspNetCore.TestBase 已经安装在 `.Web.Tests` 项目中.
-此包提供的`AbpAspNetCoreIntegratedTestBase`作为派生测试类的基类. 上面使用的`MyProjectWebTestBase`继承自`AbpAspNetCoreIntegratedTestBase`, 因此我们间接继承了`AbpAspNetCoreIntegratedTestBase`.
-
-### 基本属性
-
-`AbpAspNetCoreIntegratedTestBase` 提供了测试中使用的以下基本属性:
-
-* `Server`: 在测试中托管web应用程序的`TestServer`实例.
-* `Client`: 为执行对测试服务器的请求配置`HttpClient`实例.
-* `ServiceProvider`: 可以在你需要时处理服务提供服务.
-
-### 基本方法
-
-`AbpAspNetCoreIntegratedTestBase` 提供了以下方法, 如果需要自定义测试服务器, 可以重写这些方法:
-
-* `ConfigureServices` 仅为派生测试类注册/替换服务时可以重写使用.
-* `CreateHostBuilder` 可用于自定义生成 `IHostBuilder`.
+此包提供的`AbpWebApplicationFactoryIntegratedTest`作为派生测试类的基类. 它继承自ASP.NET Core提供的[WebApplicationFactory](https://learn.microsoft.com/zh-cn/aspnet/core/test/integration-tests)类。
+上面使用的`MyProjectWebTestBase`继承自`AbpWebApplicationFactoryIntegratedTest`, 因此我们间接继承了`AbpWebApplicationFactoryIntegratedTest`.
另请参阅
+* [ASP.NET Core 中的集成测试](https://learn.microsoft.com/zh-cn/aspnet/core/test/integration-tests)
* [总览/服务器端测试](../../Testing.md)
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs
index b5866c5a68..24f68ef9ab 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/DaprAspNetCore/AbpDaprEndpointRouteBuilderExtensions.cs
@@ -30,7 +30,7 @@ namespace Dapr
///
/// An optional delegate used to configure the subscriptions.
///
- public Func, Task> SubscriptionsCallback { get; set; }
+ public Func, Task>? SubscriptionsCallback { get; set; }
}
///
@@ -41,32 +41,32 @@ namespace Dapr
///
/// Gets or sets the topic name.
///
- public string Topic { get; set; }
+ public string Topic { get; set; } = default!;
///
/// Gets or sets the pubsub name
///
- public string PubsubName { get; set; }
+ public string PubsubName { get; set; } = default!;
///
/// Gets or sets the route
///
- public string Route { get; set; }
+ public string? Route { get; set; }
///
/// Gets or sets the routes
///
- public AbpRoutes Routes { get; set; }
+ public AbpRoutes? Routes { get; set; }
///
/// Gets or sets the metadata.
///
- public AbpMetadata Metadata { get; set; }
+ public AbpMetadata? Metadata { get; set; }
///
/// Gets or sets the deadletter topic.
///
- public string DeadLetterTopic { get; set; }
+ public string? DeadLetterTopic { get; set; }
}
///
@@ -99,12 +99,12 @@ namespace Dapr
///
/// Gets or sets the default route
///
- public string Default { get; set; }
+ public string? Default { get; set; }
///
/// Gets or sets the routing rules
///
- public List Rules { get; set; }
+ public List? Rules { get; set; }
}
///
@@ -115,12 +115,12 @@ namespace Dapr
///
/// Gets or sets the CEL expression to match this route.
///
- public string Match { get; set; }
+ public string Match { get; set; } = default!;
///
/// Gets or sets the path of the route.
///
- public string Path { get; set; }
+ public string Path { get; set; } = default!;
}
}
@@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Builder
return CreateSubscribeEndPoint(endpoints, options);
}
- private static IEndpointConventionBuilder CreateSubscribeEndPoint(IEndpointRouteBuilder endpoints, AbpSubscribeOptions options = null)
+ private static IEndpointConventionBuilder CreateSubscribeEndPoint(IEndpointRouteBuilder endpoints, AbpSubscribeOptions? options = null)
{
if (endpoints is null)
{
@@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Builder
return endpoints.MapGet("dapr/subscribe", async context =>
{
- var logger = context.RequestServices.GetRequiredService().CreateLogger("DaprTopicSubscription");
+ var logger = context.RequestServices.GetService()?.CreateLogger("DaprTopicSubscription");
var dataSource = context.RequestServices.GetRequiredService();
var subscriptions = dataSource.Endpoints
.OfType()
@@ -185,7 +185,7 @@ namespace Microsoft.AspNetCore.Builder
var topicMetadata = e.Metadata.GetOrderedMetadata();
var originalTopicMetadata = e.Metadata.GetOrderedMetadata();
- var subs = new List<(string PubsubName, string Name, string DeadLetterTopic, bool? EnableRawPayload, string Match, int Priority, Dictionary OriginalTopicMetadata, string MetadataSeparator, RoutePattern RoutePattern)>();
+ var subs = new List<(string PubsubName, string Name, string? DeadLetterTopic, bool? EnableRawPayload, string Match, int Priority, Dictionary OriginalTopicMetadata, string? MetadataSeparator, RoutePattern RoutePattern)>();
for (int i = 0; i < topicMetadata.Count(); i++)
{
@@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Builder
{
var first = e.First();
var rawPayload = e.Any(e => e.EnableRawPayload.GetValueOrDefault());
- var metadataSeparator = e.FirstOrDefault(e => !string.IsNullOrEmpty(e.MetadataSeparator)).MetadataSeparator ?? ",";
+ var metadataSeparator = e.FirstOrDefault(e => !string.IsNullOrEmpty(e.MetadataSeparator)).MetadataSeparator?.ToString() ?? ",";
var rules = e.Where(e => !string.IsNullOrEmpty(e.Match)).ToList();
var defaultRoutes = e.Where(e => string.IsNullOrEmpty(e.Match)).Select(e => RoutePatternToString(e.RoutePattern)).ToList();
var defaultRoute = defaultRoutes.FirstOrDefault();
@@ -276,7 +276,7 @@ namespace Microsoft.AspNetCore.Builder
.OrderBy(e => (e.PubsubName, e.Topic))
.ToList();
- await options?.SubscriptionsCallback(subscriptions);
+ await options?.SubscriptionsCallback!(subscriptions)!;
await context.Response.WriteAsync(JsonSerializer.Serialize(subscriptions,
new JsonSerializerOptions
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj
index f31ded0e06..96b196b67f 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ Nullable
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs
index b1d5bac7fb..946c39f59c 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs
@@ -41,8 +41,8 @@ public class AbpAspNetCoreMvcDaprEventsController : AbpController
}
else
{
- var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic));
- await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic), eventData);
+ var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic!));
+ await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic!), eventData);
}
return Ok();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj
index 03bb453ba2..1839113d06 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo.Abp.AspNetCore.Mvc.Dapr.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ Nullable
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs
index dd0c6a5300..aa7d17072b 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprAppApiTokenValidator.cs
@@ -50,9 +50,9 @@ public class DaprAppApiTokenValidator : IDaprAppApiTokenValidator, ISingletonDep
return expectedAppApiToken == headerAppApiToken;
}
- public virtual string GetDaprAppApiTokenOrNull()
+ public virtual string? GetDaprAppApiTokenOrNull()
{
- string apiTokenHeader = HttpContext.Request.Headers["dapr-api-token"];
+ string? apiTokenHeader = HttpContext.Request.Headers["dapr-api-token"];
if (string.IsNullOrEmpty(apiTokenHeader) || apiTokenHeader.Length < 1)
{
return null;
@@ -61,7 +61,7 @@ public class DaprAppApiTokenValidator : IDaprAppApiTokenValidator, ISingletonDep
return apiTokenHeader;
}
- protected virtual string GetConfiguredAppApiTokenOrNull()
+ protected virtual string? GetConfiguredAppApiTokenOrNull()
{
return HttpContext
.RequestServices
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs
index aa81765b12..e73eaec4f7 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/DaprHttpContextExtensions.cs
@@ -22,7 +22,7 @@ public static class DaprHttpContextExtensions
.IsValidDaprAppApiToken();
}
- public static string GetDaprAppApiTokenOrNull(HttpContext httpContext)
+ public static string? GetDaprAppApiTokenOrNull(HttpContext httpContext)
{
return httpContext
.RequestServices
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs
index 66fd833b77..f54f0aa8cf 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Dapr/Volo/Abp/AspNetCore/Mvc/Dapr/IDaprAppApiTokenValidator.cs
@@ -6,5 +6,5 @@ public interface IDaprAppApiTokenValidator
bool IsValidDaprAppApiToken();
- string GetDaprAppApiTokenOrNull();
+ string? GetDaprAppApiTokenOrNull();
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Zxcvbn/ZxcvbnScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Zxcvbn/ZxcvbnScriptContributor.cs
new file mode 100644
index 0000000000..8c02fc2be2
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Zxcvbn/ZxcvbnScriptContributor.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
+
+namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Zxcvbn;
+
+public class ZxcvbnScriptContributor : BundleContributor
+{
+ public override void ConfigureBundle(BundleConfigurationContext context)
+ {
+ context.Files.AddIfNotContains("/libs/zxcvbn/zxcvbn.js");
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
index 9e3b0bfba2..f1d014b76a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
@@ -145,7 +145,7 @@ public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvide
ActionApiDescriptionModel.Create(
uniqueMethodName,
method,
- apiDescription.RelativePath,
+ apiDescription.RelativePath!,
apiDescription.HttpMethod,
GetSupportedVersions(controllerType, method, setting),
allowAnonymous,
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs
index 1b8e7fcb1b..a8a2c2db54 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs
@@ -20,6 +20,8 @@ public class AbpConventionalControllerOptions
///
public bool UseV3UrlStyle { get; set; }
+ public string[] IgnoredUrlSuffixesInControllerNames { get; set; } = new[] { "Integration" };
+
public AbpConventionalControllerOptions()
{
ConventionalControllerSettings = new ConventionalControllerSettingList();
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs
index 7e5b364b3e..58dda9fb92 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs
@@ -109,7 +109,7 @@ public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDep
{
if (configuration?.UrlControllerNameNormalizer == null)
{
- return controllerName;
+ return controllerName.RemovePostFix(Options.IgnoredUrlSuffixesInControllerNames);
}
return configuration.UrlControllerNameNormalizer(
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs
index 8d5f2d4c4a..1301a5e823 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/Metadata/AbpModelMetadataProvider.cs
@@ -40,11 +40,11 @@ public class AbpModelMetadataProvider : DefaultModelMetadataProvider
{
foreach (var validationAttribute in detail.ModelAttributes.Attributes.OfType())
{
- NormalizeValidationAttrbute(validationAttribute);
+ NormalizeValidationAttribute(validationAttribute);
}
}
- protected virtual void NormalizeValidationAttrbute(ValidationAttribute validationAttribute)
+ protected virtual void NormalizeValidationAttribute(ValidationAttribute validationAttribute)
{
if (validationAttribute.ErrorMessage == null)
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
index b912d3905a..d4fd27d889 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
@@ -32,7 +32,7 @@ public class ServiceProxyGenerationModel
public ProxyScriptingModel CreateOptions()
{
- var options = new ProxyScriptingModel(Type, UseCache);
+ var options = new ProxyScriptingModel(Type!, UseCache);
if (!Modules.IsNullOrEmpty())
{
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs
index 505c07a3e1..cd87c55ea4 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs
@@ -1,14 +1,15 @@
-using System.ComponentModel.DataAnnotations;
+using System;
+using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.Validation;
public static class ValidationAttributeHelper
{
- private static readonly PropertyInfo ValidationAttributeErrorMessageStringProperty = typeof(ValidationAttribute)
+ private readonly static PropertyInfo ValidationAttributeErrorMessageStringProperty = typeof(ValidationAttribute)
.GetProperty("ErrorMessageString", BindingFlags.Instance | BindingFlags.NonPublic)!;
- private static readonly PropertyInfo ValidationAttributeCustomErrorMessageSetProperty = typeof(ValidationAttribute)
+ private readonly static PropertyInfo ValidationAttributeCustomErrorMessageSetProperty = typeof(ValidationAttribute)
.GetProperty("CustomErrorMessageSet", BindingFlags.Instance | BindingFlags.NonPublic)!;
public static void SetDefaultErrorMessage(ValidationAttribute validationAttribute)
@@ -24,7 +25,14 @@ public static class ValidationAttributeHelper
}
}
- validationAttribute.ErrorMessage =
- ValidationAttributeErrorMessageStringProperty.GetValue(validationAttribute) as string;
+ try
+ {
+ var errorMessageString = ValidationAttributeErrorMessageStringProperty.GetValue(validationAttribute) as string;
+ validationAttribute.ErrorMessage = errorMessageString;
+ }
+ catch (Exception e)
+ {
+ // ignored
+ }
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
index a6f6a55f39..5bf1cc54aa 100644
--- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
+++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
@@ -28,6 +28,7 @@
+
diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs
index 8892623364..e6c49d475c 100644
--- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs
+++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs
@@ -13,6 +13,7 @@ using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.TestBase;
+[Obsolete("Use AbpWebApplicationFactoryIntegratedTest instead.")]
public class AbpAspNetCoreAsyncIntegratedTestBase
where TModule : IAbpModule
{
diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
index c362d5074c..a42cbae457 100644
--- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
+++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
@@ -14,6 +14,7 @@ namespace Volo.Abp.AspNetCore.TestBase;
///
/// Can be a module type or old-style ASP.NET Core Startup class.
///
+[Obsolete("Use AbpWebApplicationFactoryIntegratedTest instead.")]
public abstract class AbpAspNetCoreIntegratedTestBase : AbpTestBaseWithServiceProvider, IDisposable
where TStartupModule : class
{
@@ -41,6 +42,7 @@ public abstract class AbpAspNetCoreIntegratedTestBase : AbpTestB
protected virtual IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
+ .AddAppSettingsSecretsJson()
.ConfigureWebHostDefaults(webBuilder =>
{
if (typeof(TStartupModule).IsAssignableTo())
@@ -51,7 +53,7 @@ public abstract class AbpAspNetCoreIntegratedTestBase : AbpTestB
{
webBuilder.UseStartup();
}
-
+
webBuilder.UseAbpTestServer();
})
.UseAutofac()
diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs
new file mode 100644
index 0000000000..9a6422ca97
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpWebApplicationFactoryIntegratedTest.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Volo.Abp.AspNetCore.TestBase;
+
+public abstract class AbpWebApplicationFactoryIntegratedTest : WebApplicationFactory
+ where TProgram : class
+{
+ protected HttpClient Client { get; set; }
+
+ protected IServiceProvider ServiceProvider => Services;
+
+ protected AbpWebApplicationFactoryIntegratedTest()
+ {
+ Client = CreateClient(new WebApplicationFactoryClientOptions
+ {
+ AllowAutoRedirect = false
+ });
+ ServiceProvider.GetRequiredService().Server = Server;
+ }
+
+ protected override IHost CreateHost(IHostBuilder builder)
+ {
+ builder
+ .AddAppSettingsSecretsJson()
+ .ConfigureServices(ConfigureServices);
+ return base.CreateHost(builder);
+ }
+
+ protected virtual T? GetService()
+ {
+ return Services.GetService();
+ }
+
+ protected virtual T GetRequiredService() where T : notnull
+ {
+ return Services.GetRequiredService();
+ }
+
+ protected virtual void ConfigureServices(IServiceCollection services)
+ {
+
+ }
+
+ #region GetUrl
+
+ ///
+ /// Gets default URL for given controller type.
+ ///
+ /// The type of the controller.
+ protected virtual string GetUrl()
+ {
+ return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "IntService", "IntegrationService", "Service");
+ }
+
+ ///
+ /// Gets default URL for given controller type's given action.
+ ///
+ /// The type of the controller.
+ protected virtual string GetUrl(string actionName)
+ {
+ return GetUrl() + "/" + actionName;
+ }
+
+ ///
+ /// Gets default URL for given controller type's given action with query string parameters (as anonymous object).
+ ///
+ /// The type of the controller.
+ protected virtual string GetUrl(string actionName, object queryStringParamsAsAnonymousObject)
+ {
+ var url = GetUrl(actionName);
+
+ var dictionary = new RouteValueDictionary(queryStringParamsAsAnonymousObject);
+ if (dictionary.Any())
+ {
+ url += "?" + dictionary.Select(d => $"{d.Key}={d.Value}").JoinAsString("&");
+ }
+
+ return url;
+ }
+
+ #endregion
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/WebApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/WebApplicationBuilderExtensions.cs
new file mode 100644
index 0000000000..403bbac7b1
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/WebApplicationBuilderExtensions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.AspNetCore.TestBase;
+
+public static class WebApplicationBuilderExtensions
+{
+ public async static Task RunAbpModuleAsync(this WebApplicationBuilder builder, Action? optionsAction = null)
+ where TModule : IAbpModule
+ {
+ var assemblyName = typeof(TModule).Assembly.GetName()?.Name;
+ if (!assemblyName.IsNullOrWhiteSpace())
+ {
+ // Set the application name as the assembly name of the module will automatically add assembly to the ApplicationParts of MVC application.
+ builder.Environment.ApplicationName = assemblyName!;
+ }
+ builder.Host.UseAutofac();
+ await builder.AddApplicationAsync(optionsAction);
+ var app = builder.Build();
+ await app.InitializeApplicationAsync();
+ await app.RunAsync();
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
index 83deaef26a..0c797a8dc2 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AbpAuditingMiddleware.cs
@@ -98,13 +98,13 @@ public class AbpAuditingMiddleware : IMiddleware, ITransientDependency
{
return false;
}
-
- if (!AuditingOptions.IsEnabledForIntegrationServices &&
+
+ if (!AuditingOptions.IsEnabledForIntegrationServices &&
context.Request.Path.Value.StartsWith($"/{AbpAspNetCoreConsts.DefaultIntegrationServiceApiPrefix}/"))
{
return true;
}
-
+
if (AspNetCoreAuditingOptions.IgnoredUrls.Any(x => context.Request.Path.Value.StartsWith(x)))
{
return true;
@@ -134,7 +134,8 @@ public class AbpAuditingMiddleware : IMiddleware, ITransientDependency
}
if (!AuditingOptions.IsEnabledForGetRequests &&
- string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase))
+ (string.Equals(httpContext.Request.Method, HttpMethods.Get, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(httpContext.Request.Method, HttpMethods.Head, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs
index 8e9f14e976..e3e8f22d0a 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogScope.cs
@@ -1,9 +1,6 @@
-using JetBrains.Annotations;
-
-namespace Volo.Abp.Auditing;
+namespace Volo.Abp.Auditing;
public interface IAuditLogScope
{
- [NotNull]
AuditLogInfo Log { get; }
}
diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingManager.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingManager.cs
index a1091b8600..c496a22340 100644
--- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingManager.cs
+++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingManager.cs
@@ -1,6 +1,4 @@
-using JetBrains.Annotations;
-
-namespace Volo.Abp.Auditing;
+namespace Volo.Abp.Auditing;
public interface IAuditingManager
{
diff --git a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs
similarity index 96%
rename from framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
rename to framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs
index 58047f575d..ea746500d6 100644
--- a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
+++ b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleObjectExtensions.cs
@@ -1,12 +1,11 @@
using System.Collections.Generic;
-using Volo.Abp;
using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace AutoMapper;
-public static class AbpAutoMapperExtensibleDtoExtensions
+public static class AbpAutoMapperExtensibleObjectExtensions
{
public static IMappingExpression MapExtraProperties(
this IMappingExpression mappingExpression,
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/AzureServiceBusMessageConsumerFactory.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/AzureServiceBusMessageConsumerFactory.cs
index 04c7b48c6a..cf92ed7b73 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/AzureServiceBusMessageConsumerFactory.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/AzureServiceBusMessageConsumerFactory.cs
@@ -14,7 +14,7 @@ public class AzureServiceBusMessageConsumerFactory : IAzureServiceBusMessageCons
ServiceScope = serviceScopeFactory.CreateScope();
}
- public IAzureServiceBusMessageConsumer CreateMessageConsumer(string topicName, string subscriptionName, string connectionName)
+ public IAzureServiceBusMessageConsumer CreateMessageConsumer(string topicName, string subscriptionName, string? connectionName)
{
var processor = ServiceScope.ServiceProvider.GetRequiredService();
processor.Initialize(topicName, subscriptionName, connectionName);
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
index d616e1264f..df830cde7a 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/ConnectionPool.cs
@@ -28,7 +28,7 @@ public class ConnectionPool : IConnectionPool, ISingletonDependency
Logger = new NullLogger();
}
- public ServiceBusClient GetClient(string connectionName)
+ public ServiceBusClient GetClient(string? connectionName)
{
connectionName ??= AzureServiceBusConnections.DefaultConnectionName;
return _clients.GetOrAdd(
@@ -40,7 +40,7 @@ public class ConnectionPool : IConnectionPool, ISingletonDependency
).Value;
}
- public ServiceBusAdministrationClient GetAdministrationClient(string connectionName)
+ public ServiceBusAdministrationClient GetAdministrationClient(string? connectionName)
{
connectionName ??= AzureServiceBusConnections.DefaultConnectionName;
return _adminClients.GetOrAdd(
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IAzureServiceBusMessageConsumerFactory.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IAzureServiceBusMessageConsumerFactory.cs
index bd770e2204..4d93bcf5c3 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IAzureServiceBusMessageConsumerFactory.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IAzureServiceBusMessageConsumerFactory.cs
@@ -16,5 +16,5 @@ public interface IAzureServiceBusMessageConsumerFactory
IAzureServiceBusMessageConsumer CreateMessageConsumer(
string topicName,
string subscriptionName,
- string connectionName);
+ string? connectionName);
}
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IConnectionPool.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IConnectionPool.cs
index a4cdfbc122..825c981a00 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IConnectionPool.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IConnectionPool.cs
@@ -6,7 +6,7 @@ namespace Volo.Abp.AzureServiceBus;
public interface IConnectionPool : IAsyncDisposable
{
- ServiceBusClient GetClient(string connectionName);
+ ServiceBusClient GetClient(string? connectionName);
- ServiceBusAdministrationClient GetAdministrationClient(string connectionName);
+ ServiceBusAdministrationClient GetAdministrationClient(string? connectionName);
}
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IPublisherPool.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IPublisherPool.cs
index 4940d250dd..bb0bf8e827 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IPublisherPool.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/IPublisherPool.cs
@@ -6,5 +6,5 @@ namespace Volo.Abp.AzureServiceBus;
public interface IPublisherPool : IAsyncDisposable
{
- Task GetAsync(string topicName, string connectionName);
+ Task GetAsync(string topicName, string? connectionName);
}
diff --git a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/PublisherPool.cs b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/PublisherPool.cs
index 45b025cc59..a10c63868d 100644
--- a/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/PublisherPool.cs
+++ b/framework/src/Volo.Abp.AzureServiceBus/Volo/Abp/AzureServiceBus/PublisherPool.cs
@@ -24,7 +24,7 @@ public class PublisherPool : IPublisherPool, ISingletonDependency
Logger = new NullLogger();
}
- public async Task GetAsync(string topicName, string connectionName)
+ public async Task GetAsync(string topicName, string? connectionName)
{
var admin = _connectionPool.GetAdministrationClient(connectionName);
await admin.SetupTopicAsync(topicName);
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
index a6bb742585..ee064c4b67 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
+++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
@@ -23,6 +23,7 @@
+
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
index 676b42081f..5b00d15c9e 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
@@ -8,12 +8,14 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using StackExchange.Redis;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Bundling;
using Volo.Abp.Cli.Commands.Services;
using Volo.Abp.Cli.LIbs;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building;
+using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectModification;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
@@ -89,6 +91,8 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
var projectArgs = await GetProjectBuildArgsAsync(commandLineArgs, template, projectName);
+ await CheckCreatingRequirements(projectArgs);
+
var result = await TemplateProjectBuilder.BuildAsync(
projectArgs
);
@@ -97,7 +101,10 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
Logger.LogInformation($"'{projectName}' has been successfully created to '{projectArgs.OutputFolder}'");
+ await CheckCreatedRequirements(projectArgs);
+
ConfigureNpmPackagesForTheme(projectArgs);
+ await CreateOpenIddictPfxFilesAsync(projectArgs);
await RunGraphBuildForMicroserviceServiceTemplate(projectArgs);
await CreateInitialMigrationsAsync(projectArgs);
@@ -119,6 +126,46 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
OpenRelatedWebPage(projectArgs, template, isTiered, commandLineArgs);
}
+ private Task CheckCreatingRequirements(ProjectBuildArgs projectArgs)
+ {
+ return Task.CompletedTask;
+ }
+
+ private async Task CheckCreatedRequirements(ProjectBuildArgs projectArgs)
+ {
+ var errors = new List();
+
+ if (projectArgs.ExtraProperties.ContainsKey("PreRequirements:Redis"))
+ {
+ var isConnected = false;
+ try
+ {
+ var redis = await ConnectionMultiplexer.ConnectAsync("127.0.0.1", options => options.ConnectTimeout = 3000);
+ isConnected = redis.IsConnected;
+ }
+ catch (Exception e)
+ {
+ // ignored
+ }
+ finally
+ {
+ if (!isConnected)
+ {
+ errors.Add("\t* Redis is not installed or not running on your computer.");
+ }
+ }
+ }
+
+ if (errors.Any())
+ {
+ Logger.LogWarning("NOTICE: The following tools are required to run your solution.");
+ foreach (var error in errors)
+ {
+ Logger.LogWarning(error);
+ }
+ }
+ }
+
public string GetUsageInfo()
{
var sb = new StringBuilder();
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
index 3e83cd818d..4eff9df5e6 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
@@ -16,6 +16,7 @@ using Volo.Abp.Cli.LIbs;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Events;
+using Volo.Abp.Cli.ProjectBuilding.Templates;
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Microservice;
using Volo.Abp.Cli.ProjectBuilding.Templates.Module;
@@ -481,6 +482,41 @@ public abstract class ProjectCreationCommandBase
await InitialMigrationCreator.CreateAsync(Path.GetDirectoryName(efCoreProjectPath), isLayeredTemplate);
}
+ protected Task CreateOpenIddictPfxFilesAsync(ProjectBuildArgs projectArgs)
+ {
+ if (!projectArgs.ExtraProperties.ContainsKey(nameof(RandomizeAuthServerPassPhraseStep)))
+ {
+ return Task.CompletedTask;
+ }
+
+ var module = projectArgs.ExtraProperties[nameof(RandomizeAuthServerPassPhraseStep)];
+ if (string.IsNullOrWhiteSpace(module))
+ {
+ return Task.CompletedTask;
+ }
+
+ var moduleDirectory = projectArgs.OutputFolder + module;
+ if (projectArgs.UiFramework != UiFramework.Angular)
+ {
+ moduleDirectory = moduleDirectory.Replace("/aspnet-core/", "/");
+ }
+
+ moduleDirectory = Path.GetDirectoryName(projectArgs.SolutionName.CompanyName == null
+ ? moduleDirectory.Replace("MyCompanyName.MyProjectName", projectArgs.SolutionName.ProjectName)
+ : moduleDirectory.Replace("MyCompanyName", projectArgs.SolutionName.CompanyName).Replace("MyProjectName", projectArgs.SolutionName.ProjectName));
+
+ if (Directory.Exists(moduleDirectory))
+ {
+ Logger.LogInformation($"Creating openiddict.pfx file on {moduleDirectory}");
+ CmdHelper.RunCmd($"dotnet dev-certs https -ep openiddict.pfx -p {RandomizeAuthServerPassPhraseStep.RandomOpenIddictPassword}", moduleDirectory);
+ }
+ else
+ {
+ Logger.LogWarning($"Couldn't find the module directory to create openiddict.pfx file: {moduleDirectory}");
+ }
+ return Task.CompletedTask;
+ }
+
protected async Task ConfigurePwaSupportForAngular(ProjectBuildArgs projectArgs)
{
var isAngular = projectArgs.UiFramework == UiFramework.Angular;
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs
index 88bdc27973..d33aa103ba 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
+using Volo.Abp.Cli.ProjectBuilding.Templates;
namespace Volo.Abp.Cli.ProjectBuilding.Building;
@@ -28,7 +29,14 @@ public abstract class TemplateInfo
public virtual IEnumerable GetCustomSteps(ProjectBuildContext context)
{
- return Array.Empty();
+ var steps = new List();
+ ConfigureCheckPreRequirements(context, steps);
+ return steps;
+ }
+
+ protected void ConfigureCheckPreRequirements(ProjectBuildContext context, List steps)
+ {
+ steps.Add(new CheckRedisPreRequirements());
}
public bool IsPro()
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs
index ca5f5ceb61..edf8728305 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
@@ -20,7 +21,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
public override IEnumerable GetCustomSteps(ProjectBuildContext context)
{
- var steps = new List();
+ var steps = base.GetCustomSteps(context).ToList();
switch (context.BuildArgs.DatabaseProvider)
{
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
index e8522f6f19..612dc51bd2 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
@@ -28,7 +28,7 @@ public abstract class AppTemplateBase : TemplateInfo
public override IEnumerable GetCustomSteps(ProjectBuildContext context)
{
- var steps = new List();
+ var steps = base.GetCustomSteps(context).ToList();
ConfigureTenantSchema(context, steps);
SwitchDatabaseProvider(context, steps);
@@ -174,6 +174,7 @@ public abstract class AppTemplateBase : TemplateInfo
{
steps.Add(new MauiChangeApplicationIdGuidStep());
steps.Add(new MauiChangePortStep());
+ context.Symbols.Add("mobile:maui");
}
else
{
@@ -192,10 +193,12 @@ public abstract class AppTemplateBase : TemplateInfo
context.BuildArgs.ExtraProperties.ContainsKey("separate-auth-server"))
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Public"));
+ context.Symbols.Add("ui:mvc-public-host");
}
else
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Public.Host"));
+ context.Symbols.Add("ui:mvc-public");
}
}
}
@@ -399,6 +402,7 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.AuthServer"));
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host"));
+ context.Symbols.Add("HostWithIds");
steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305"));
}
}
@@ -424,6 +428,7 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.AuthServer"));
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host"));
+ context.Symbols.Add("HostWithIds");
steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305"));
}
@@ -517,6 +522,7 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.AuthServer"));
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host"));
+ context.Symbols.Add("HostWithIds");
steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305"));
}
@@ -554,6 +560,7 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.AuthServer"));
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host"));
+ context.Symbols.Add("HostWithIds");
steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305"));
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/CheckRedisPreRequirements.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/CheckRedisPreRequirements.cs
new file mode 100644
index 0000000000..aee6da8f99
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/CheckRedisPreRequirements.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Linq;
+using Volo.Abp.Cli.ProjectBuilding.Building;
+
+namespace Volo.Abp.Cli.ProjectBuilding.Templates;
+
+public class CheckRedisPreRequirements : ProjectBuildPipelineStep
+{
+ public override void Execute(ProjectBuildContext context)
+ {
+ var modules = context.Files.Where(f => f.Name.EndsWith("Module.cs", StringComparison.OrdinalIgnoreCase));
+ if (modules.Any(module => module.Content.Contains("Redis:Configuration")))
+ {
+ context.BuildArgs.ExtraProperties["PreRequirements:Redis"] = "true";
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
index 13674458ee..e5ce4a0f84 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceServiceTemplateBase.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
@@ -33,14 +34,14 @@ public abstract class MicroserviceServiceTemplateBase : TemplateInfo
public override IEnumerable GetCustomSteps(ProjectBuildContext context)
{
- var steps = new List();
+ var steps = base.GetCustomSteps(context).ToList();
DeleteUnrelatedUiProject(context, steps);
SetRandomPortForHostProject(context, steps);
RandomizeStringEncryption(context, steps);
RandomizeAuthServerPassPhrase(context, steps);
ChangeConnectionString(context, steps);
-
+
return steps;
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
index b8d320fb45..cb7722da10 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Microservice/MicroserviceTemplateBase.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
@@ -19,7 +20,7 @@ public abstract class MicroserviceTemplateBase : TemplateInfo
public override IEnumerable GetCustomSteps(ProjectBuildContext context)
{
- var steps = new List();
+ var steps = base.GetCustomSteps(context).ToList();
DeleteUnrelatedProjects(context, steps);
RandomizeStringEncryption(context, steps);
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs
index 5229bb167f..428c1061a1 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
@@ -21,7 +22,7 @@ public abstract class ModuleTemplateBase : TemplateInfo
public override IEnumerable GetCustomSteps(ProjectBuildContext context)
{
- var steps = new List();
+ var steps = base.GetCustomSteps(context).ToList();
DeleteUnrelatedProjects(context, steps);
RandomizeSslPorts(context, steps);
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RandomizeAuthServerPassPhraseStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RandomizeAuthServerPassPhraseStep.cs
index 7d42977c97..a41c870f41 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RandomizeAuthServerPassPhraseStep.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RandomizeAuthServerPassPhraseStep.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Volo.Abp.Cli.ProjectBuilding.Building;
@@ -6,16 +7,30 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates;
public class RandomizeAuthServerPassPhraseStep : ProjectBuildPipelineStep
{
- protected const string DefaultPassPhrase = "00000000-0000-0000-0000-000000000000";
+ private const string DefaultPassword = "00000000-0000-0000-0000-000000000000";
+ private const string KestrelCertificatesDefaultPassword = "Kestrel__Certificates__Default__Password=00000000-0000-0000-0000-000000000000";
+ private const string LocalhostPfx = "localhost.pfx -p 00000000-0000-0000-0000-000000000000";
+ private const string DotnetDevCerts = "openiddict.pfx -p 00000000-0000-0000-0000-000000000000";
+ private const string ProductionEncryptionAndSigningCertificate = "AddProductionEncryptionAndSigningCertificate(\"openiddict.pfx\", \"00000000-0000-0000-0000-000000000000\");";
+ private readonly static string RandomPassword = Guid.NewGuid().ToString("D");
+ public readonly static string RandomOpenIddictPassword = Guid.NewGuid().ToString("D");
public override void Execute(ProjectBuildContext context)
{
var files = context.Files
.Where(x => !x.IsDirectory)
- .Where(x => x.Content.IndexOf(DefaultPassPhrase, StringComparison.InvariantCultureIgnoreCase) >= 0)
+ .Where(x => x.Name.EndsWith(".cs") ||
+ x.Name.EndsWith(".json") ||
+ x.Name.EndsWith(".yml") ||
+ x.Name.EndsWith(".yaml") ||
+ x.Name.EndsWith(".md") ||
+ x.Name.EndsWith(".ps1") ||
+ x.Name.EndsWith(".sh") ||
+ x.Name.Contains("Dockerfile"))
+ .Where(x => x.Content.IndexOf(DefaultPassword, StringComparison.InvariantCultureIgnoreCase) >= 0)
.ToList();
- var randomPassPhrase = Guid.NewGuid().ToString("D");
+ string module = null;
foreach (var file in files)
{
file.NormalizeLineEndings();
@@ -23,13 +38,43 @@ public class RandomizeAuthServerPassPhraseStep : ProjectBuildPipelineStep
var lines = file.GetLines();
for (var i = 0; i < lines.Length; i++)
{
- if (lines[i].Contains(DefaultPassPhrase))
+ if (lines[i].Contains(KestrelCertificatesDefaultPassword))
{
- lines[i] = lines[i].Replace(DefaultPassPhrase, randomPassPhrase);
+ lines[i] = lines[i].Replace(KestrelCertificatesDefaultPassword,
+ KestrelCertificatesDefaultPassword.Replace(DefaultPassword,
+ RandomPassword));
+ }
+
+ if (lines[i].Contains(LocalhostPfx))
+ {
+ lines[i] = lines[i].Replace(LocalhostPfx,
+ LocalhostPfx.Replace(DefaultPassword,
+ RandomPassword));
+ }
+
+ if (lines[i].Contains(DotnetDevCerts))
+ {
+ lines[i] = lines[i].Replace(DotnetDevCerts,
+ DotnetDevCerts.Replace(DefaultPassword,
+ RandomOpenIddictPassword));
+ }
+
+ if (lines[i].Contains(ProductionEncryptionAndSigningCertificate))
+ {
+ lines[i] = lines[i].Replace(ProductionEncryptionAndSigningCertificate,
+ ProductionEncryptionAndSigningCertificate.Replace(DefaultPassword,
+ RandomOpenIddictPassword));
+
+ module = file.Name;
}
}
file.SetLines(lines);
}
+
+ if (!module.IsNullOrWhiteSpace())
+ {
+ context.BuildArgs.ExtraProperties[nameof(RandomizeAuthServerPassPhraseStep)] = module;
+ }
}
}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
index 535b68ef59..ba85c48aeb 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
@@ -22,6 +22,7 @@ using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Json;
+using System.Text.RegularExpressions;
namespace Volo.Abp.Cli.ProjectModification;
@@ -114,12 +115,11 @@ public class SolutionModuleAdder : ITransientDependency
var projectFiles = ProjectFinder.GetProjectFiles(solutionFile);
await AddNugetAndNpmReferences(module, projectFiles, !(newTemplate || newProTemplate));
-
+
var modulesFolderInSolution = Path.Combine(Path.GetDirectoryName(solutionFile), "modules");
if (withSourceCode || newTemplate || newProTemplate)
{
-
await PublishEventAsync(5, $"Downloading source code of {moduleName}");
await DownloadSourceCodesToSolutionFolder(module, modulesFolderInSolution, version, newTemplate, newProTemplate);
@@ -147,6 +147,8 @@ public class SolutionModuleAdder : ITransientDependency
else
{
await AddAngularPackages(solutionFile, module);
+
+ await TryConfigureModuleConfigurationsForAngular(solutionFile, module);
}
await RunBundleForBlazorAsync(projectFiles, module);
@@ -167,10 +169,83 @@ public class SolutionModuleAdder : ITransientDependency
return module;
}
+ private async Task TryConfigureModuleConfigurationsForAngular(string solutionFilePath, ModuleWithMastersInfo module)
+ {
+ var angularPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(solutionFilePath)), "angular");
+
+ if (!Directory.Exists(angularPath))
+ {
+ return;
+ }
+
+ var angularPackages = module.NpmPackages?
+ .Where(p => p.ApplicationType.HasFlag(NpmApplicationType.Angular))
+ .ToList();
+
+ if (!angularPackages.Any())
+ {
+ return;
+ }
+
+ await PublishEventAsync(6, "Configuring angular projects...");
+
+ var moduleName = module.Name.Split('.').Last();
+
+ ConfigureAngularPackagesForAppModuleFile(angularPath, angularPackages, moduleName);
+
+ ConfigureAngularPackagesForAppRoutingModuleFile(angularPath, angularPackages, moduleName);
+ }
+
+ private void ConfigureAngularPackagesForAppModuleFile(string angularPath, List angularPackages, string moduleName)
+ {
+ var appModulePath = Path.Combine(angularPath, "src", "app", "app.module.ts");
+ if (!File.Exists(appModulePath))
+ {
+ return;
+ }
+
+ var appModuleFileContent = File.ReadAllText(appModulePath);
+
+ foreach (var angularPackage in angularPackages)
+ {
+ var moduleNameAsConfigPath = angularPackage.Name.EnsureStartsWith('@').EnsureEndsWith('/') + "config";
+
+ appModuleFileContent = "import { " + moduleName + "ConfigModule } from '" + moduleNameAsConfigPath + "';" + Environment.NewLine + appModuleFileContent;
+ appModuleFileContent = Regex.Replace(appModuleFileContent, "imports\\s*:\\s*\\[",
+ "imports: [" + Environment.NewLine +
+ " " + moduleName + "ConfigModule.forRoot(),");
+ }
+
+ File.WriteAllText(appModulePath, appModuleFileContent);
+ }
+
+ private void ConfigureAngularPackagesForAppRoutingModuleFile(string angularPath, List angularPackages, string moduleName)
+ {
+ var appRoutingModulePath = Path.Combine(angularPath, "src", "app", "app-routing.module.ts");
+ if (!File.Exists(appRoutingModulePath))
+ {
+ return;
+ }
+
+ var appRoutingModuleFileContent = File.ReadAllText(appRoutingModulePath);
+
+ foreach (var angularPackage in angularPackages)
+ {
+ appRoutingModuleFileContent = Regex.Replace(appRoutingModuleFileContent, "Routes\\s*=\\s*\\[",
+ "Routes = [" + Environment.NewLine +
+ " " + "{" + Environment.NewLine +
+ " " + "path: '" + moduleName.ToLower() + "'," + Environment.NewLine +
+ " " + "loadChildren: () => " + $"import('{angularPackage.Name.EnsureStartsWith('@')}').then(m => m.{moduleName}Module.forLazy())," + Environment.NewLine +
+ " " + "},");
+ }
+
+ File.WriteAllText(appRoutingModulePath, appRoutingModuleFileContent);
+ }
+
private async Task SetLeptonXAbpVersionsAsync(string solutionFile, string combine)
{
var abpVersion = SolutionPackageVersionFinder.FindByCsprojVersion(solutionFile);
-
+
var projects = Directory.GetFiles(Path.GetDirectoryName(solutionFile)!, "*.csproj", SearchOption.AllDirectories);
foreach (var project in projects)
@@ -183,7 +258,8 @@ public class SolutionModuleAdder : ITransientDependency
private async Task PublishEventAsync(int currentStep, string message)
{
- await LocalEventBus.PublishAsync(new ModuleInstallingProgressEvent {
+ await LocalEventBus.PublishAsync(new ModuleInstallingProgressEvent
+ {
CurrentStep = currentStep,
Message = message
}, false);
@@ -562,7 +638,7 @@ public class SolutionModuleAdder : ITransientDependency
if (webPackagesWillBeAddedToBlazorServerProject)
{
- if ( nugetTarget == NuGetPackageTarget.Web)
+ if (nugetTarget == NuGetPackageTarget.Web)
{
nugetTarget = NuGetPackageTarget.BlazorServer;
}
@@ -636,7 +712,7 @@ public class SolutionModuleAdder : ITransientDependency
}
var dbMigrationsProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrations.csproj"))
- ?? projectFiles.FirstOrDefault(p => p.EndsWith(".EntityFrameworkCore.csproj")) ;
+ ?? projectFiles.FirstOrDefault(p => p.EndsWith(".EntityFrameworkCore.csproj"));
if (dbMigrationsProject == null)
{
diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs
index f31feff873..88bf37982e 100644
--- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs
+++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs
@@ -18,11 +18,12 @@ public static class ConfigurationHelper
var builder = new ConfigurationBuilder()
.SetBasePath(options.BasePath!)
- .AddJsonFile(options.FileName + ".json", optional: options.Optional, reloadOnChange: options.ReloadOnChange);
+ .AddJsonFile(options.FileName + ".json", optional: options.Optional, reloadOnChange: options.ReloadOnChange)
+ .AddJsonFile(options.FileName + ".secrets.json", optional: true, reloadOnChange: options.ReloadOnChange);
if (!options.EnvironmentName.IsNullOrEmpty())
{
- builder = builder.AddJsonFile($"{options.FileName}.{options.EnvironmentName}.json", optional: options.Optional, reloadOnChange: options.ReloadOnChange);
+ builder = builder.AddJsonFile($"{options.FileName}.{options.EnvironmentName}.json", optional: true, reloadOnChange: options.ReloadOnChange);
}
if (options.EnvironmentName == "Development")
diff --git a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
index c7153264ae..3d44fa988a 100644
--- a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
+++ b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
@@ -47,7 +47,7 @@ public static class AbpStringExtensions
/// Indicates whether this string is null or an System.String.Empty string.
///
[ContractAnnotation("str:null => true")]
- public static bool IsNullOrEmpty(this string? str)
+ public static bool IsNullOrEmpty([System.Diagnostics.CodeAnalysis.NotNullWhen(false)]this string? str)
{
return string.IsNullOrEmpty(str);
}
@@ -56,7 +56,7 @@ public static class AbpStringExtensions
/// indicates whether this string is null, empty, or consists only of white-space characters.
///
[ContractAnnotation("str:null => true")]
- public static bool IsNullOrWhiteSpace(this string? str)
+ public static bool IsNullOrWhiteSpace([System.Diagnostics.CodeAnalysis.NotNullWhen(false)]this string? str)
{
return string.IsNullOrWhiteSpace(str);
}
diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
index c250d07eee..9709051409 100644
--- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
+++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj
@@ -37,4 +37,10 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs
index 8602792701..3791c4202f 100644
--- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs
+++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs
@@ -132,7 +132,7 @@ public abstract class AbstractKeyReadOnlyAppService ((IHasCreationTime)e).CreationTime);
}
- throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!");
+ throw new AbpException("No sorting specified but this query requires sorting. Override the ApplySorting or the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!");
}
///
diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
index ba82e5bdc2..4eb285df04 100644
--- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
+++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
@@ -1,4 +1,3 @@
-using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
@@ -41,7 +40,7 @@ public abstract class ApplicationService :
[Obsolete("Use LazyServiceProvider instead.")]
public IServiceProvider ServiceProvider { get; set; } = default!;
- public static string[] CommonPostfixes { get; set; } = { "AppService", "ApplicationService", "IntService", "IntegrationService", "Service" };
+ public static string[] CommonPostfixes { get; set; } = { "AppService", "ApplicationService", "Service" };
public List AppliedCrossCuttingConcerns { get; } = new();
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs
index c63a5442c7..a877c673f1 100644
--- a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs
+++ b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs
@@ -1,5 +1,6 @@
using System;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
@@ -17,13 +18,13 @@ public static class ServiceCollectionRepositoryExtensions
var readOnlyBasicRepositoryInterface = typeof(IReadOnlyBasicRepository<>).MakeGenericType(entityType);
if (readOnlyBasicRepositoryInterface.IsAssignableFrom(repositoryImplementationType))
{
- RegisterService(services, readOnlyBasicRepositoryInterface, repositoryImplementationType, replaceExisting);
+ RegisterService(services, readOnlyBasicRepositoryInterface, repositoryImplementationType, replaceExisting, true);
//IReadOnlyRepository
var readOnlyRepositoryInterface = typeof(IReadOnlyRepository<>).MakeGenericType(entityType);
if (readOnlyRepositoryInterface.IsAssignableFrom(repositoryImplementationType))
{
- RegisterService(services, readOnlyRepositoryInterface, repositoryImplementationType, replaceExisting);
+ RegisterService(services, readOnlyRepositoryInterface, repositoryImplementationType, replaceExisting, true);
}
//IBasicRepository
@@ -48,13 +49,13 @@ public static class ServiceCollectionRepositoryExtensions
var readOnlyBasicRepositoryInterfaceWithPk = typeof(IReadOnlyBasicRepository<,>).MakeGenericType(entityType, primaryKeyType);
if (readOnlyBasicRepositoryInterfaceWithPk.IsAssignableFrom(repositoryImplementationType))
{
- RegisterService(services, readOnlyBasicRepositoryInterfaceWithPk, repositoryImplementationType, replaceExisting);
+ RegisterService(services, readOnlyBasicRepositoryInterfaceWithPk, repositoryImplementationType, replaceExisting, true);
//IReadOnlyRepository
var readOnlyRepositoryInterfaceWithPk = typeof(IReadOnlyRepository<,>).MakeGenericType(entityType, primaryKeyType);
if (readOnlyRepositoryInterfaceWithPk.IsAssignableFrom(repositoryImplementationType))
{
- RegisterService(services, readOnlyRepositoryInterfaceWithPk, repositoryImplementationType, replaceExisting);
+ RegisterService(services, readOnlyRepositoryInterfaceWithPk, repositoryImplementationType, replaceExisting, true);
}
//IBasicRepository
@@ -80,15 +81,33 @@ public static class ServiceCollectionRepositoryExtensions
IServiceCollection services,
Type serviceType,
Type implementationType,
- bool replaceExisting)
+ bool replaceExisting,
+ bool isReadOnlyRepository = false)
{
+ ServiceDescriptor descriptor;
+
+ if (isReadOnlyRepository)
+ {
+ services.TryAddTransient(implementationType);
+ descriptor = ServiceDescriptor.Transient(serviceType, provider =>
+ {
+ var repository = provider.GetRequiredService(implementationType);
+ ObjectHelper.TrySetProperty(repository.As(), x => x.IsChangeTrackingEnabled, _ => false);
+ return repository;
+ });
+ }
+ else
+ {
+ descriptor = ServiceDescriptor.Transient(serviceType, implementationType);
+ }
+
if (replaceExisting)
{
- services.Replace(ServiceDescriptor.Transient(serviceType, implementationType));
+ services.Replace(descriptor);
}
else
{
- services.TryAddTransient(serviceType, implementationType);
+ services.TryAdd(descriptor);
}
}
}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs
index 536e31e51f..de6632704d 100644
--- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs
@@ -2,6 +2,7 @@
using Volo.Abp.Auditing;
using Volo.Abp.Caching;
using Volo.Abp.Data;
+using Volo.Abp.Domain.ChangeTracking;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus;
using Volo.Abp.ExceptionHandling;
@@ -30,5 +31,6 @@ public class AbpDddDomainModule : AbpModule
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddConventionalRegistrar(new AbpRepositoryConventionalRegistrar());
+ context.Services.OnRegistered(ChangeTrackingInterceptorRegistrar.RegisterIfNeeded);
}
}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingHelper.cs
new file mode 100644
index 0000000000..d2235afd22
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingHelper.cs
@@ -0,0 +1,53 @@
+using System.Linq;
+using System.Reflection;
+using JetBrains.Annotations;
+using Volo.Abp.Domain.Repositories;
+
+namespace Volo.Abp.Domain.ChangeTracking;
+
+public static class ChangeTrackingHelper
+{
+ public static bool IsEntityChangeTrackingType(TypeInfo implementationType)
+ {
+ return HasEntityChangeTrackingAttribute(implementationType) || AnyMethodHasEntityChangeTrackingAttribute(implementationType);
+ }
+
+ public static bool IsEntityChangeTrackingMethod([NotNull] MethodInfo methodInfo, out EntityChangeTrackingAttribute? entityChangeTrackingAttribute)
+ {
+ Check.NotNull(methodInfo, nameof(methodInfo));
+
+ //Method declaration
+ var attrs = methodInfo.GetCustomAttributes(true).OfType().ToArray();
+ if (attrs.Any())
+ {
+ entityChangeTrackingAttribute = attrs.First();
+ return true;
+ }
+
+ if (methodInfo.DeclaringType != null)
+ {
+ //Class declaration
+ attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType().ToArray();
+ if (attrs.Any())
+ {
+ entityChangeTrackingAttribute = attrs.First();
+ return true;
+ }
+ }
+
+ entityChangeTrackingAttribute = null;
+ return false;
+ }
+
+ private static bool AnyMethodHasEntityChangeTrackingAttribute(TypeInfo implementationType)
+ {
+ return implementationType
+ .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
+ .Any(HasEntityChangeTrackingAttribute);
+ }
+
+ private static bool HasEntityChangeTrackingAttribute(MemberInfo memberInfo)
+ {
+ return memberInfo.IsDefined(typeof(EntityChangeTrackingAttribute), true);
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptor.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptor.cs
new file mode 100644
index 0000000000..307e2dee6a
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptor.cs
@@ -0,0 +1,30 @@
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.DynamicProxy;
+
+namespace Volo.Abp.Domain.ChangeTracking;
+
+public class ChangeTrackingInterceptor : AbpInterceptor, ITransientDependency
+{
+ private readonly IEntityChangeTrackingProvider _entityChangeTrackingProvider;
+
+ public ChangeTrackingInterceptor(IEntityChangeTrackingProvider entityChangeTrackingProvider)
+ {
+ _entityChangeTrackingProvider = entityChangeTrackingProvider;
+ }
+
+ public async override Task InterceptAsync(IAbpMethodInvocation invocation)
+ {
+ if (!ChangeTrackingHelper.IsEntityChangeTrackingMethod(invocation.Method, out var changeTrackingAttribute))
+ {
+ await invocation.ProceedAsync();
+ return;
+ }
+
+ using (_entityChangeTrackingProvider.Change(changeTrackingAttribute?.IsEnabled))
+ {
+ await invocation.ProceedAsync();
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptorRegistrar.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptorRegistrar.cs
new file mode 100644
index 0000000000..0249c570dd
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/ChangeTrackingInterceptorRegistrar.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Reflection;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.DynamicProxy;
+
+namespace Volo.Abp.Domain.ChangeTracking;
+
+public class ChangeTrackingInterceptorRegistrar
+{
+ public static void RegisterIfNeeded(IOnServiceRegistredContext context)
+ {
+ if (ShouldIntercept(context.ImplementationType))
+ {
+ context.Interceptors.TryAdd();
+ }
+ }
+
+ private static bool ShouldIntercept(Type type)
+ {
+ return !DynamicProxyIgnoreTypes.Contains(type) && ChangeTrackingHelper.IsEntityChangeTrackingType(type.GetTypeInfo());
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/DisableEntityChangeTrackingAttribute.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/DisableEntityChangeTrackingAttribute.cs
new file mode 100644
index 0000000000..98011bda5f
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/DisableEntityChangeTrackingAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Volo.Abp.Domain.ChangeTracking;
+
+///
+/// Ensures that the change tracking in enabled for the given method or class.
+///
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
+public class DisableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute
+{
+ public DisableEntityChangeTrackingAttribute()
+ : base(false)
+ {
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EnableEntityChangeTrackingAttribute.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EnableEntityChangeTrackingAttribute.cs
new file mode 100644
index 0000000000..542b60cd74
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EnableEntityChangeTrackingAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Volo.Abp.Domain.ChangeTracking;
+
+///
+/// Ensures that the change tracking in enabled for the given method or class.
+///
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
+public class EnableEntityChangeTrackingAttribute : EntityChangeTrackingAttribute
+{
+ public EnableEntityChangeTrackingAttribute()
+ : base(true)
+ {
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EntityChangeTrackingAttribute.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EntityChangeTrackingAttribute.cs
new file mode 100644
index 0000000000..3446a49354
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/ChangeTracking/EntityChangeTrackingAttribute.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Volo.Abp.Domain.ChangeTracking;
+
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
+public abstract class EntityChangeTrackingAttribute : Attribute
+{
+ public virtual bool IsEnabled { get; set; }
+
+ public EntityChangeTrackingAttribute(bool isEnabled)
+ {
+ IsEnabled = isEnabled;
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
index 0d86045eea..cd85acce50 100644
--- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@@ -34,6 +36,14 @@ public abstract class BasicRepositoryBase :
public ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetService(NullCancellationTokenProvider.Instance);
+ public ILoggerFactory? LoggerFactory => LazyServiceProvider.LazyGetService();
+
+ public ILogger Logger => LazyServiceProvider.LazyGetService(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance);
+
+ public IEntityChangeTrackingProvider EntityChangeTrackingProvider => LazyServiceProvider.LazyGetRequiredService();
+
+ public bool? IsChangeTrackingEnabled { get; protected set; }
+
protected BasicRepositoryBase()
{
@@ -106,6 +116,24 @@ public abstract class BasicRepositoryBase :
{
return CancellationTokenProvider.FallbackToProvider(preferredValue);
}
+
+ protected virtual bool ShouldTrackingEntityChange()
+ {
+ // If IsChangeTrackingEnabled is set, it has the highest priority. This generally means the repository is read-only.
+ if (IsChangeTrackingEnabled.HasValue)
+ {
+ return IsChangeTrackingEnabled.Value;
+ }
+
+ // If Interface/Class/Method has Enable/DisableEntityChangeTrackingAttribute, it has the second highest priority.
+ if (EntityChangeTrackingProvider.Enabled.HasValue)
+ {
+ return EntityChangeTrackingProvider.Enabled.Value;
+ }
+
+ // Default behavior is tracking entity change.
+ return true;
+ }
}
public abstract class BasicRepositoryBase : BasicRepositoryBase, IBasicRepository
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs
new file mode 100644
index 0000000000..19c10e9a3f
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/EntityChangeTrackingProvider.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Threading;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Domain.Repositories;
+
+public class EntityChangeTrackingProvider : IEntityChangeTrackingProvider, ISingletonDependency
+{
+ public bool? Enabled => _current.Value;
+
+ private readonly AsyncLocal _current = new AsyncLocal();
+
+ public IDisposable Change(bool? enabled)
+ {
+ var previousValue = Enabled;
+ _current.Value = enabled;
+ return new DisposeAction(() => _current.Value = previousValue);
+ }
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs
new file mode 100644
index 0000000000..f1db1584fa
--- /dev/null
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IEntityChangeTrackingProvider.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Volo.Abp.Domain.Repositories;
+
+public interface IEntityChangeTrackingProvider
+{
+ bool? Enabled { get; }
+
+ IDisposable Change(bool? enabled);
+}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs
index 3c7ae81875..dc39255b25 100644
--- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs
@@ -12,7 +12,7 @@ namespace Volo.Abp.Domain.Repositories;
///
public interface IRepository
{
-
+ bool? IsChangeTrackingEnabled { get; }
}
public interface IRepository : IReadOnlyRepository, IBasicRepository
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs
index 1c51954f57..08c1f2556f 100644
--- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/ISupportsExplicitLoading.cs
@@ -7,8 +7,8 @@ using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Domain.Repositories;
-public interface ISupportsExplicitLoading
- where TEntity : class, IEntity
+public interface ISupportsExplicitLoading
+ where TEntity : class, IEntity
{
Task EnsureCollectionLoadedAsync(
TEntity entity,
@@ -18,7 +18,7 @@ public interface ISupportsExplicitLoading
Task EnsurePropertyLoadedAsync(
TEntity entity,
- Expression> propertyExpression,
+ Expression> propertyExpression,
CancellationToken cancellationToken)
where TProperty : class;
}
diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
index cbebf62196..3c43798c38 100644
--- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
+++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
@@ -15,16 +15,16 @@ namespace Volo.Abp.Domain.Repositories;
public static class RepositoryExtensions
{
- public async static Task EnsureCollectionLoadedAsync(
- this IBasicRepository repository,
+ public async static Task EnsureCollectionLoadedAsync(
+ this IBasicRepository repository,
TEntity entity,
Expression>> propertyExpression,
CancellationToken cancellationToken = default
)
- where TEntity : class, IEntity
+ where TEntity : class, IEntity
where TProperty : class
{
- var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading;
+ var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading;
if (repo != null)
{
await repo.EnsureCollectionLoadedAsync(entity, propertyExpression, cancellationToken);
@@ -34,13 +34,13 @@ public static class RepositoryExtensions
public async static Task EnsurePropertyLoadedAsync(
this IBasicRepository repository,
TEntity entity,
- Expression> propertyExpression,
+ Expression> propertyExpression,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity
where TProperty : class
{
- var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading;
+ var repo = ProxyHelper.UnProxy(repository) as ISupportsExplicitLoading;
if (repo != null)
{
await repo.EnsurePropertyLoadedAsync(entity, propertyExpression, cancellationToken);
@@ -60,12 +60,12 @@ public static class RepositoryExtensions
}
}
- public async static Task EnsureExistsAsync(
- this IRepository repository,
+ public async static Task EnsureExistsAsync(
+ this IRepository repository,
Expression> expression,
CancellationToken cancellationToken = default
)
- where TEntity : class, IEntity
+ where TEntity : class, IEntity
{
if (!await repository.AnyAsync(expression, cancellationToken))
{
@@ -145,6 +145,40 @@ public static class RepositoryExtensions
}
}
+ ///
+ /// Disables change tracking mechanism for the given repository.
+ ///
+ /// A repository object
+ ///
+ /// A disposable object. Dispose it to restore change tracking mechanism back to its previous state.
+ ///
+ public static IDisposable DisableTracking(this IRepository repository)
+ {
+ return Tracking(repository, false);
+ }
+
+ ///
+ /// Enables change tracking mechanism for the given repository.
+ ///
+ /// A repository object
+ ///
+ /// A disposable object. Dispose it to restore change tracking mechanism back to its previous state.
+ ///
+ public static IDisposable EnableTracking(this IRepository repository)
+ {
+ return Tracking(repository, true);
+ }
+
+ private static IDisposable Tracking(this IRepository repository, bool enabled)
+ {
+ var previous = repository.IsChangeTrackingEnabled;
+ ObjectHelper.TrySetProperty(ProxyHelper.UnProxy(repository).As(), x => x.IsChangeTrackingEnabled, _ => enabled);
+ return new DisposeAction(_ =>
+ {
+ ObjectHelper.TrySetProperty(ProxyHelper.UnProxy(repository).As(), x => x.IsChangeTrackingEnabled, _ => previous);
+ }, repository);
+ }
+
private static IUnitOfWorkManager GetUnitOfWorkManager(
this IBasicRepository repository,
[CallerMemberName] string callingMethodName = nameof(GetUnitOfWorkManager)
diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AdditionalEmailSendingArgs.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AdditionalEmailSendingArgs.cs
new file mode 100644
index 0000000000..4de1eb6105
--- /dev/null
+++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AdditionalEmailSendingArgs.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using Volo.Abp.Data;
+
+namespace Volo.Abp.Emailing;
+
+[Serializable]
+public class AdditionalEmailSendingArgs
+{
+ public List? CC { get; set; }
+
+ public List? Attachments { get; set; }
+
+ public ExtraPropertyDictionary? ExtraProperties { get; set; }
+}
diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs
index 07b4ca9bcd..ac12ddcead 100644
--- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs
+++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/BackgroundEmailSendingJob.cs
@@ -15,15 +15,15 @@ public class BackgroundEmailSendingJob : AsyncBackgroundJob
public bool IsBodyHtml { get; set; } = true;
- //TODO: Add other properties and attachments
+ public AdditionalEmailSendingArgs? AdditionalEmailSendingArgs { get; set; }
}
diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailAttachment.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailAttachment.cs
new file mode 100644
index 0000000000..e2fe3a3a6b
--- /dev/null
+++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailAttachment.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Volo.Abp.Emailing;
+
+[Serializable]
+public class EmailAttachment
+{
+ public string? Name { get; set; }
+
+ public byte[]? File { get; set; }
+}
diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs
index d8a46ca4b9..5f342b2f40 100644
--- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs
+++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/EmailSenderBase.cs
@@ -1,4 +1,6 @@
using System;
+using System.IO;
+using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
@@ -24,20 +26,44 @@ public abstract class EmailSenderBase : IEmailSender
BackgroundJobManager = backgroundJobManager;
}
- public virtual async Task SendAsync(string to, string? subject, string? body, bool isBodyHtml = true)
+ public virtual async Task SendAsync(string to, string? subject, string? body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null)
{
- await SendAsync(new MailMessage
- {
- To = { to },
- Subject = subject,
- Body = body,
- IsBodyHtml = isBodyHtml
- });
+ await SendAsync(BuildMailMessage(null, to, subject, body, isBodyHtml, additionalEmailSendingArgs));
+ }
+
+ public virtual async Task SendAsync(string from, string to, string? subject, string? body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null)
+ {
+ await SendAsync(BuildMailMessage(from, to, subject, body, isBodyHtml, additionalEmailSendingArgs));
}
- public virtual async Task SendAsync(string from, string to, string? subject, string? body, bool isBodyHtml = true)
+ protected virtual MailMessage BuildMailMessage(string? from, string to, string? subject, string? body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null)
{
- await SendAsync(new MailMessage(from, to, subject, body) { IsBodyHtml = isBodyHtml });
+ var message = from == null
+ ? new MailMessage { To = { to }, Subject = subject, Body = body, IsBodyHtml = isBodyHtml }
+ : new MailMessage(from, to, subject, body) { IsBodyHtml = isBodyHtml };
+
+ if (additionalEmailSendingArgs != null)
+ {
+ if (additionalEmailSendingArgs.Attachments != null)
+ {
+ foreach (var attachment in additionalEmailSendingArgs.Attachments.Where(x => x.File != null))
+ {
+ var fileStream = new MemoryStream(attachment.File!);
+ fileStream.Seek(0, SeekOrigin.Begin);
+ message.Attachments.Add(new Attachment(fileStream, attachment.Name));
+ }
+ }
+
+ if (additionalEmailSendingArgs.CC != null)
+ {
+ foreach (var cc in additionalEmailSendingArgs.CC)
+ {
+ message.CC.Add(cc);
+ }
+ }
+ }
+
+ return message;
}
public virtual async Task SendAsync(MailMessage mail, bool normalize = true)
@@ -50,11 +76,11 @@ public abstract class EmailSenderBase : IEmailSender
await SendEmailAsync(mail);
}
- public virtual async Task QueueAsync(string to, string subject, string body, bool isBodyHtml = true)
+ public virtual async Task QueueAsync(string to, string subject, string body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null)
{
if (!BackgroundJobManager.IsAvailable())
{
- await SendAsync(to, subject, body, isBodyHtml);
+ await SendAsync(to, subject, body, isBodyHtml, additionalEmailSendingArgs);
return;
}
@@ -64,16 +90,17 @@ public abstract class EmailSenderBase : IEmailSender
To = to,
Subject = subject,
Body = body,
- IsBodyHtml = isBodyHtml
+ IsBodyHtml = isBodyHtml,
+ AdditionalEmailSendingArgs = additionalEmailSendingArgs
}
);
}
- public virtual async Task QueueAsync(string from, string to, string subject, string body, bool isBodyHtml = true)
+ public virtual async Task QueueAsync(string from, string to, string subject, string body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null)
{
if (!BackgroundJobManager.IsAvailable())
{
- await SendAsync(from, to, subject, body, isBodyHtml);
+ await SendAsync(from, to, subject, body, isBodyHtml, additionalEmailSendingArgs);
return;
}
@@ -84,7 +111,8 @@ public abstract class EmailSenderBase : IEmailSender
To = to,
Subject = subject,
Body = body,
- IsBodyHtml = isBodyHtml
+ IsBodyHtml = isBodyHtml,
+ AdditionalEmailSendingArgs = additionalEmailSendingArgs
}
);
}
diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/IEmailSender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/IEmailSender.cs
index 55456783c4..64658b0519 100644
--- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/IEmailSender.cs
+++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/IEmailSender.cs
@@ -15,7 +15,8 @@ public interface IEmailSender
string to,
string? subject,
string? body,
- bool isBodyHtml = true
+ bool isBodyHtml = true,
+ AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
///
@@ -26,7 +27,8 @@ public interface IEmailSender
string to,
string? subject,
string? body,
- bool isBodyHtml = true
+ bool isBodyHtml = true,
+ AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
///
@@ -49,7 +51,8 @@ public interface IEmailSender
string to,
string subject,
string body,
- bool isBodyHtml = true
+ bool isBodyHtml = true,
+ AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
///
@@ -60,8 +63,7 @@ public interface IEmailSender
string to,
string subject,
string body,
- bool isBodyHtml = true
+ bool isBodyHtml = true,
+ AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
-
- //TODO: Add other Queue methods too. Problem: MailMessage is not serializable so can not be used in background jobs.
}
diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSender.cs
index 99a8f2e1e7..4bf7e18158 100644
--- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSender.cs
+++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Smtp/SmtpEmailSender.cs
@@ -67,7 +67,7 @@ public class SmtpEmailSender : EmailSenderBase, ISmtpEmailSender, ITransientDepe
}
}
- protected override async Task SendEmailAsync(MailMessage mail)
+ protected async override Task SendEmailAsync(MailMessage mail)
{
using (var smtpClient = await BuildClientAsync())
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
index b19feda6c3..d470ed692e 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCore.MySQLVolo.Abp.EntityFrameworkCore.MySQL$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs
index 596062d533..07381983ca 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextMySQLExtensions.cs
@@ -10,7 +10,7 @@ public static class AbpDbContextConfigurationContextMySQLExtensions
{
public static DbContextOptionsBuilder UseMySQL(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action mySQLOptionsAction = null)
+ Action? mySQLOptionsAction = null)
{
if (context.ExistingConnection != null)
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs
index 247e96cee7..baf8c53a14 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsMySQLExtensions.cs
@@ -8,7 +8,7 @@ public static class AbpDbContextOptionsMySQLExtensions
{
public static void UseMySQL(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action mySQLOptionsAction = null)
+ Action? mySQLOptionsAction = null)
{
options.Configure(context =>
{
@@ -18,7 +18,7 @@ public static class AbpDbContextOptionsMySQLExtensions
public static void UseMySQL(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action mySQLOptionsAction = null)
+ Action? mySQLOptionsAction = null)
where TDbContext : AbpDbContext
{
options.Configure(context =>
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj
index 2ead0da1f3..dcd5fb1788 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCore.Oracle.DevartVolo.Abp.EntityFrameworkCore.Oracle.Devart$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs
index a672a280ee..e2138dd7d1 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleDevartExtensions.cs
@@ -10,7 +10,7 @@ public static class AbpDbContextConfigurationContextOracleDevartExtensions
{
public static DbContextOptionsBuilder UseOracle(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action oracleOptionsAction = null,
+ Action? oracleOptionsAction = null,
bool useExistingConnectionIfAvailable = false)
{
if (useExistingConnectionIfAvailable && context.ExistingConnection != null)
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs
index 12f5bc0437..650d222663 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleDevartExtensions.cs
@@ -8,7 +8,7 @@ public static class AbpDbContextOptionsOracleDevartExtensions
{
public static void UseOracle(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action oracleOptionsAction = null,
+ Action? oracleOptionsAction = null,
bool useExistingConnectionIfAvailable = false)
{
options.Configure(context =>
@@ -19,7 +19,7 @@ public static class AbpDbContextOptionsOracleDevartExtensions
public static void UseOracle(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action oracleOptionsAction = null,
+ Action? oracleOptionsAction = null,
bool useExistingConnectionIfAvailable = false)
where TDbContext : AbpDbContext
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj
index 187c6a1d9f..3ce2f5ff25 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCore.OracleVolo.Abp.EntityFrameworkCore.Oracle$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleExtensions.cs
index 9b9d3b2916..39f3a5b86f 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextOracleExtensions.cs
@@ -10,7 +10,7 @@ public static class AbpDbContextConfigurationContextOracleExtensions
{
public static DbContextOptionsBuilder UseOracle(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action oracleOptionsAction = null)
+ Action? oracleOptionsAction = null)
{
if (context.ExistingConnection != null)
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleExtensions.cs
index cc0dc28699..8458dd6d4e 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsOracleExtensions.cs
@@ -8,7 +8,7 @@ public static class AbpDbContextOptionsOracleExtensions
{
public static void UseOracle(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action oracleOptionsAction = null)
+ Action? oracleOptionsAction = null)
{
options.Configure(context =>
{
@@ -18,7 +18,7 @@ public static class AbpDbContextOptionsOracleExtensions
public static void UseOracle(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action oracleOptionsAction = null)
+ Action? oracleOptionsAction = null)
where TDbContext : AbpDbContext
{
options.Configure(context =>
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj
index 67f55beeb4..04da1dd18a 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo.Abp.EntityFrameworkCore.PostgreSql.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCore.PostgreSqlVolo.Abp.EntityFrameworkCore.PostgreSql$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextPostgreSqlExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextPostgreSqlExtensions.cs
index 7e13fb01a2..918c0629c1 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextPostgreSqlExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextPostgreSqlExtensions.cs
@@ -11,14 +11,14 @@ public static class AbpDbContextConfigurationContextPostgreSqlExtensions
[Obsolete("Use 'UseNpgsql(...)' method instead. This will be removed in future versions.")]
public static DbContextOptionsBuilder UsePostgreSql(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action postgreSqlOptionsAction = null)
+ Action? postgreSqlOptionsAction = null)
{
return context.UseNpgsql(postgreSqlOptionsAction);
}
public static DbContextOptionsBuilder UseNpgsql(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action postgreSqlOptionsAction = null)
+ Action? postgreSqlOptionsAction = null)
{
if (context.ExistingConnection != null)
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsPostgreSqlExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsPostgreSqlExtensions.cs
index bdea54efab..2a52c5de56 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsPostgreSqlExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsPostgreSqlExtensions.cs
@@ -9,7 +9,7 @@ public static class AbpDbContextOptionsPostgreSqlExtensions
[Obsolete("Use 'UseNpgsql(...)' method instead. This will be removed in future versions.")]
public static void UsePostgreSql(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action postgreSqlOptionsAction = null)
+ Action? postgreSqlOptionsAction = null)
{
options.Configure(context =>
{
@@ -20,7 +20,7 @@ public static class AbpDbContextOptionsPostgreSqlExtensions
[Obsolete("Use 'UseNpgsql(...)' method instead. This will be removed in future versions.")]
public static void UsePostgreSql(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action postgreSqlOptionsAction = null)
+ Action? postgreSqlOptionsAction = null)
where TDbContext : AbpDbContext
{
options.Configure(context =>
@@ -31,7 +31,7 @@ public static class AbpDbContextOptionsPostgreSqlExtensions
public static void UseNpgsql(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action postgreSqlOptionsAction = null)
+ Action? postgreSqlOptionsAction = null)
{
options.Configure(context =>
{
@@ -41,7 +41,7 @@ public static class AbpDbContextOptionsPostgreSqlExtensions
public static void UseNpgsql(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action postgreSqlOptionsAction = null)
+ Action? postgreSqlOptionsAction = null)
where TDbContext : AbpDbContext
{
options.Configure(context =>
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/ConnectionStrings/NpgsqlConnectionStringChecker.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/ConnectionStrings/NpgsqlConnectionStringChecker.cs
index f3ab83eb55..56e47aa2d4 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/ConnectionStrings/NpgsqlConnectionStringChecker.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/ConnectionStrings/NpgsqlConnectionStringChecker.cs
@@ -25,7 +25,7 @@ public class NpgsqlConnectionStringChecker : IConnectionStringChecker, ITransien
await using var conn = new NpgsqlConnection(connString.ConnectionString);
await conn.OpenAsync();
result.Connected = true;
- await conn.ChangeDatabaseAsync(oldDatabaseName);
+ await conn.ChangeDatabaseAsync(oldDatabaseName!);
result.DatabaseExists = true;
await conn.CloseAsync();
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj
index f655b66814..0fb7ee0c61 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCore.SqlServerVolo.Abp.EntityFrameworkCore.SqlServer$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqlServerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqlServerExtensions.cs
index 76476423bb..95ea2ebd8b 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqlServerExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqlServerExtensions.cs
@@ -10,7 +10,7 @@ public static class AbpDbContextConfigurationContextSqlServerExtensions
{
public static DbContextOptionsBuilder UseSqlServer(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action sqlServerOptionsAction = null)
+ Action? sqlServerOptionsAction = null)
{
if (context.ExistingConnection != null)
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqlServerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqlServerExtensions.cs
index 30bf419d5e..8981d8f0c4 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqlServerExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqlServerExtensions.cs
@@ -8,7 +8,7 @@ public static class AbpDbContextOptionsSqlServerExtensions
{
public static void UseSqlServer(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action sqlServerOptionsAction = null)
+ Action? sqlServerOptionsAction = null)
{
options.Configure(context =>
{
@@ -18,7 +18,7 @@ public static class AbpDbContextOptionsSqlServerExtensions
public static void UseSqlServer(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action sqlServerOptionsAction = null)
+ Action? sqlServerOptionsAction = null)
where TDbContext : AbpDbContext
{
options.Configure(context =>
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj
index 002b876343..e57c897169 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCore.SqliteVolo.Abp.EntityFrameworkCore.Sqlite$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs
index 6dea2a336b..a4e3b60ec9 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs
@@ -11,7 +11,7 @@ public static class AbpDbContextConfigurationContextSqliteExtensions
{
public static DbContextOptionsBuilder UseSqlite(
[NotNull] this AbpDbContextConfigurationContext context,
- [CanBeNull] Action sqliteOptionsAction = null)
+ Action? sqliteOptionsAction = null)
{
if (context.ExistingConnection != null)
{
@@ -34,7 +34,7 @@ public static class AbpDbContextConfigurationContextSqliteExtensions
public static DbContextOptionsBuilder UseSqlite(
[NotNull] this AbpDbContextConfigurationContext context,
DbConnection connection,
- [CanBeNull] Action sqliteOptionsAction = null)
+ Action? sqliteOptionsAction = null)
{
if (context.ExistingConnection != null)
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs
index aaf39cbde5..c3f24a318c 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs
@@ -8,7 +8,7 @@ public static class AbpDbContextOptionsSqliteExtensions
{
public static void UseSqlite(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action sqliteOptionsAction = null)
+ Action? sqliteOptionsAction = null)
{
options.Configure(context =>
{
@@ -18,7 +18,7 @@ public static class AbpDbContextOptionsSqliteExtensions
public static void UseSqlite(
[NotNull] this AbpDbContextOptions options,
- [CanBeNull] Action sqliteOptionsAction = null)
+ Action? sqliteOptionsAction = null)
where TDbContext : AbpDbContext
{
options.Configure(context =>
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs
index f51df050ef..55e88b72e7 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs
@@ -13,7 +13,7 @@ public static class AbpEfCoreServiceCollectionExtensions
{
public static IServiceCollection AddAbpDbContext(
this IServiceCollection services,
- Action optionsBuilder = null)
+ Action? optionsBuilder = null)
where TDbContext : AbpDbContext
{
services.AddMemoryCache();
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
index c547299bae..af2be88afa 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
@@ -5,6 +5,8 @@
net7.0
+ enable
+ NullableVolo.Abp.EntityFrameworkCoreVolo.Abp.EntityFrameworkCore$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs
index 94dba628d7..614ba135ff 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EfCoreRepositoryExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Entities;
@@ -44,4 +45,10 @@ public static class EfCoreRepositoryExtensions
throw new ArgumentException("Given repository does not implement " + typeof(IEfCoreRepository).AssemblyQualifiedName, nameof(repository));
}
+
+ public static IQueryable AsNoTrackingIf(this IQueryable queryable, bool condition)
+ where TEntity : class, IEntity
+ {
+ return condition ? queryable.AsNoTracking() : queryable;
+ }
}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
index 03af8d1fb4..3274360ba2 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
@@ -1,4 +1,3 @@
-using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -6,16 +5,16 @@ using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
-using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
using Volo.Abp.Guids;
-using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore;
@@ -27,11 +26,11 @@ public class EfCoreRepository : RepositoryBase, IE
protected virtual TDbContext DbContext => GetDbContext();
[Obsolete("Use GetDbContextAsync() method.")]
- DbContext IEfCoreRepository.DbContext => GetDbContext() as DbContext;
+ DbContext IEfCoreRepository.DbContext => (GetDbContext() as DbContext)!;
async Task IEfCoreRepository.GetDbContextAsync()
{
- return await GetDbContextAsync() as DbContext;
+ return (await GetDbContextAsync() as DbContext)!;
}
[Obsolete("Use GetDbContextAsync() method.")]
@@ -75,13 +74,13 @@ public class EfCoreRepository : RepositoryBase, IE
{
return (await GetDbContextAsync()).Set();
}
-
+
protected async Task GetDbConnectionAsync()
{
return (await GetDbContextAsync()).Database.GetDbConnection();
}
- protected async Task GetDbTransactionAsync()
+ protected async Task GetDbTransactionAsync()
{
return (await GetDbContextAsync()).Database.CurrentTransaction?.GetDbTransaction();
}
@@ -93,7 +92,7 @@ public class EfCoreRepository : RepositoryBase, IE
public virtual IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetService(SimpleGuidGenerator.Instance);
- public IEfCoreBulkOperationProvider BulkOperationProvider => LazyServiceProvider.LazyGetService();
+ public IEfCoreBulkOperationProvider? BulkOperationProvider => LazyServiceProvider.LazyGetService();
public EfCoreRepository(IDbContextProvider dbContextProvider)
{
@@ -254,19 +253,19 @@ public class EfCoreRepository : RepositoryBase, IE
{
return includeDetails
? await (await WithDetailsAsync()).ToListAsync(GetCancellationToken(cancellationToken))
- : await (await GetDbSetAsync()).ToListAsync(GetCancellationToken(cancellationToken));
+ : await (await GetQueryableAsync()).ToListAsync(GetCancellationToken(cancellationToken));
}
public async override Task> GetListAsync(Expression> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
{
return includeDetails
? await (await WithDetailsAsync()).Where(predicate).ToListAsync(GetCancellationToken(cancellationToken))
- : await (await GetDbSetAsync()).Where(predicate).ToListAsync(GetCancellationToken(cancellationToken));
+ : await (await GetQueryableAsync()).Where(predicate).ToListAsync(GetCancellationToken(cancellationToken));
}
public async override Task GetCountAsync(CancellationToken cancellationToken = default)
{
- return await (await GetDbSetAsync()).LongCountAsync(GetCancellationToken(cancellationToken));
+ return await (await GetQueryableAsync()).LongCountAsync(GetCancellationToken(cancellationToken));
}
public async override Task> GetPagedListAsync(
@@ -278,7 +277,7 @@ public class EfCoreRepository : RepositoryBase, IE
{
var queryable = includeDetails
? await WithDetailsAsync()
- : await GetDbSetAsync();
+ : await GetQueryableAsync();
return await queryable
.OrderByIf>(!sorting.IsNullOrWhiteSpace(), sorting)
@@ -289,12 +288,12 @@ public class EfCoreRepository : RepositoryBase, IE
[Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable GetQueryable()
{
- return DbSet.AsQueryable();
+ return DbSet.AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange());
}
public async override Task> GetQueryableAsync()
{
- return (await GetDbSetAsync()).AsQueryable();
+ return (await GetDbSetAsync()).AsQueryable().AsNoTrackingIf(!ShouldTrackingEntityChange());
}
protected async override Task SaveChangesAsync(CancellationToken cancellationToken)
@@ -302,7 +301,7 @@ public class EfCoreRepository : RepositoryBase, IE
await (await GetDbContextAsync()).SaveChangesAsync(cancellationToken);
}
- public async override Task FindAsync(
+ public async override Task FindAsync(
Expression> predicate,
bool includeDetails = true,
CancellationToken cancellationToken = default)
@@ -311,7 +310,7 @@ public class EfCoreRepository : RepositoryBase, IE
? await (await WithDetailsAsync())
.Where(predicate)
.SingleOrDefaultAsync(GetCancellationToken(cancellationToken))
- : await (await GetDbSetAsync())
+ : await (await GetQueryableAsync())
.Where(predicate)
.SingleOrDefaultAsync(GetCancellationToken(cancellationToken));
}
@@ -333,7 +332,7 @@ public class EfCoreRepository : RepositoryBase, IE
}
}
- public override async Task DeleteDirectAsync(Expression> predicate, CancellationToken cancellationToken = default)
+ public async override Task DeleteDirectAsync(Expression> predicate, CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
var dbSet = dbContext.Set();
@@ -354,7 +353,7 @@ public class EfCoreRepository : RepositoryBase, IE
public virtual async Task EnsurePropertyLoadedAsync(
TEntity entity,
- Expression> propertyExpression,
+ Expression> propertyExpression,
CancellationToken cancellationToken = default)
where TProperty : class
{
@@ -442,7 +441,7 @@ public class EfCoreRepository : RepositoryBase, IE
public class EfCoreRepository : EfCoreRepository,
IEfCoreRepository,
- ISupportsExplicitLoading
+ ISupportsExplicitLoading
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
@@ -465,11 +464,13 @@ public class EfCoreRepository : EfCoreRepository FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
+ public virtual async Task FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
{
return includeDetails
- ? await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
- : await (await GetDbSetAsync()).FindAsync(new object[] { id }, GetCancellationToken(cancellationToken));
+ ? await (await WithDetailsAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken))
+ : !ShouldTrackingEntityChange()
+ ? await (await GetQueryableAsync()).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id!.Equals(id), GetCancellationToken(cancellationToken))
+ : await (await GetDbSetAsync()).FindAsync(new object[] {id!}, GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
index bc93ca97b8..05dd4527fc 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
@@ -37,7 +37,7 @@ namespace Volo.Abp.EntityFrameworkCore;
public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, ITransientDependency
where TDbContext : DbContext
{
- public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
+ public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = default!;
protected virtual Guid? CurrentTenantId => CurrentTenant?.Id;
@@ -74,21 +74,21 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
.GetMethod(
nameof(ConfigureBaseProperties),
BindingFlags.Instance | BindingFlags.NonPublic
- );
+ )!;
private static readonly MethodInfo ConfigureValueConverterMethodInfo
= typeof(AbpDbContext)
.GetMethod(
nameof(ConfigureValueConverter),
BindingFlags.Instance | BindingFlags.NonPublic
- );
+ )!;
private static readonly MethodInfo ConfigureValueGeneratedMethodInfo
= typeof(AbpDbContext)
.GetMethod(
nameof(ConfigureValueGenerated),
BindingFlags.Instance | BindingFlags.NonPublic
- );
+ )!;
protected AbpDbContext(DbContextOptions options)
: base(options)
@@ -157,7 +157,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
try
{
var auditLog = AuditingManager?.Current?.Log;
- List entityChangeList = null;
+ List? entityChangeList = null;
if (auditLog != null)
{
entityChangeList = EntityHistoryHelper.CreateChangeList(ChangeTracker.Entries().ToList());
@@ -174,7 +174,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
if (entityChangeList != null)
{
EntityHistoryHelper.UpdateChangeList(entityChangeList);
- auditLog.EntityChanges.AddRange(entityChangeList);
+ auditLog!.EntityChanges.AddRange(entityChangeList);
Logger.LogDebug($"Added {entityChangeList.Count} entity changes to the current audit log");
}
@@ -249,13 +249,13 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
}
}
- protected virtual void ChangeTracker_Tracked(object sender, EntityTrackedEventArgs e)
+ protected virtual void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
{
FillExtraPropertiesForTrackedEntities(e);
PublishEventsForTrackedEntity(e.Entry);
}
- protected virtual void ChangeTracker_StateChanged(object sender, EntityStateChangedEventArgs e)
+ protected virtual void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
{
PublishEventsForTrackedEntity(e.Entry);
}
@@ -453,11 +453,11 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
if (conversionType == typeof(Guid))
{
- entryProperty.CurrentValue = TypeDescriptor.GetConverter(conversionType).ConvertFromInvariantString(entityProperty.ToString());
+ entryProperty.CurrentValue = TypeDescriptor.GetConverter(conversionType).ConvertFromInvariantString(entityProperty.ToString()!);
}
else if (conversionType.IsEnum)
{
- entryProperty.CurrentValue = Enum.Parse(conversionType, entityProperty.ToString(), ignoreCase: true);
+ entryProperty.CurrentValue = Enum.Parse(conversionType, entityProperty.ToString()!, ignoreCase: true);
}
else
{
@@ -560,7 +560,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
return;
}
- var idProperty = entry.Property("Id").Metadata.PropertyInfo;
+ var idProperty = entry.Property("Id").Metadata.PropertyInfo!;
//Check for DatabaseGeneratedAttribute
var dbGeneratedAttr = ReflectionHelper
@@ -669,7 +669,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
}
var idPropertyBuilder = modelBuilder.Entity().Property(x => ((IEntity)x).Id);
- if (idPropertyBuilder.Metadata.PropertyInfo.IsDefined(typeof(DatabaseGeneratedAttribute), true))
+ if (idPropertyBuilder.Metadata.PropertyInfo!.IsDefined(typeof(DatabaseGeneratedAttribute), true))
{
return;
}
@@ -692,10 +692,10 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
return false;
}
- protected virtual Expression> CreateFilterExpression()
+ protected virtual Expression>? CreateFilterExpression()
where TEntity : class
{
- Expression> expression = null;
+ Expression>? expression = null;
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs
index 43803b4004..cb5b258192 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs
@@ -12,7 +12,7 @@ public class AbpDbContextOptions
{
internal List