diff --git a/.github/scripts/update_versions.py b/.github/scripts/update_versions.py index 843e80a3a6..dcbd749796 100644 --- a/.github/scripts/update_versions.py +++ b/.github/scripts/update_versions.py @@ -39,14 +39,16 @@ def create_pr(): branch=branch_name, ) - pr = repo.create_pull(title="Update latest-versions.json", - body="Automated PR to update the latest-versions.json file.", - head=branch_name, base="dev") + try: + pr = repo.create_pull(title="Update latest-versions.json", + body="Automated PR to update the latest-versions.json file.", + head=branch_name, base="dev") + except Exception as e: + print(f"Error while creating PR: {e}") pr.create_review_request(reviewers=["ebicoglu", "gizemmutukurt", "skoc10"]) -if __name__ == "__dev__": +if __name__ == "__main__": should_create_pr = update_latest_versions() if should_create_pr: - create_pr() - + create_pr() \ No newline at end of file diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml index c9d39fad30..a410be9a31 100644 --- a/.github/workflows/auto-pr.yml +++ b/.github/workflows/auto-pr.yml @@ -1,8 +1,8 @@ -name: Merge branch dev with rel-7.2 +name: Merge branch dev with rel-7.3 on: push: branches: - - rel-7.2 + - rel-7.3 permissions: contents: read @@ -18,13 +18,13 @@ jobs: ref: dev - name: Reset promotion branch run: | - git fetch origin rel-7.2:rel-7.2 - git reset --hard rel-7.2 + git fetch origin rel-7.3:rel-7.3 + git reset --hard rel-7.3 - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: - branch: auto-merge/rel-7-2/${{github.run_number}} - title: Merge branch dev with rel-7.2 - body: This PR generated automatically to merge dev with rel-7.2. Please review the changed files before merging to prevent any errors that may occur. + branch: auto-merge/rel-7-3/${{github.run_number}} + title: Merge branch dev with rel-7.3 + body: This PR generated automatically to merge dev with rel-7.3. Please review the changed files before merging to prevent any errors that may occur. reviewers: ${{github.actor}} - token: ${{ github.token }} + token: ${{ github.token }} \ No newline at end of file diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000000..b929fa6d3c --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,44 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + tag_name: + description: 'Tag Name' + required: true + prerelease: + description: 'Pre-release?' + required: true + branchName: + description: 'Branch Name' + required: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.branchName }} + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + with: + tag_name: ${{ github.event.inputs.tag_name }} + release_name: ${{ github.event.inputs.tag_name }} + draft: false + prerelease: ${{ github.event.inputs.prerelease }} + + - name: Checkout code at tag + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.tag_name }} + + - name: Build Project + run: | + # add your build commands here, depending on your project's requirements. + echo "Build project here" diff --git a/.github/workflows/update-versions.yml b/.github/workflows/update-versions.yml index 386b6858b9..d0a295884f 100644 --- a/.github/workflows/update-versions.yml +++ b/.github/workflows/update-versions.yml @@ -28,6 +28,6 @@ jobs: - name: Update latest-versions.json and create PR env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | python .github/scripts/update_versions.py diff --git a/Directory.Build.props b/Directory.Build.props index 43a884f556..2b04bcc025 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -42,6 +42,9 @@ true + + 4.5.0 + diff --git a/NuGet.md b/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index acfe23fd00..34092c44ee 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -461,6 +461,9 @@ "UnitPrice": "Unit Price", "OverallDiscountAmount": "Overall Discount Amount", "DiscountAmount": "Discount Amount", - "FullChangeHistory": "Full Change History" + "FullChangeHistory": "Full Change History", + "Permission:RefreshReleaseLogs": "Refresh Release Logs", + "ReleaseLogs": "Release Logs", + "AuthorUserName": "Author User Name" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json index 82cc7724bf..4a7e64125f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/ar.json @@ -137,6 +137,7 @@ "Address": "العنوان", "Homepage": "الصفحة الرئيسية", "Year": "السنة", + "Year_Plural": "سنوات", "Copyright": "حقوق النشر © {1}", "DomainDrivenDesign": "التصميم المُقاد بالنطاق DDD", "CrossCuttingConcerns": "اهتمامات مشتركة", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index d228bed40c..6788a4d224 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -141,6 +141,7 @@ "Address": "Address", "Homepage": "Homepage", "Year": "Year", + "Year_Plural": "Years", "Copyright": "Copyright © {1}", "DomainDrivenDesign": "Domain Driven Design", "CrossCuttingConcerns": "Cross Cutting Concerns", @@ -211,6 +212,11 @@ "RecentActivities": "Recent Activities", "SpringCampaign": "Welcome
Spring Sale!", "SpringCampaign2": "Limited
Time Offer!
", - "AboutUs": "About Us" + "AboutUs": "About Us", + "HowItWorks": "How it works?", + "ReleaseNotes": "Release Notes", + "DetailedChangeNotes" : "Detailed Change Notes", + "SeeTrainings": "See Trainings", + "NoContent": "No content" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json index cd2802fef4..16437480ef 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/fi.json @@ -140,6 +140,7 @@ "Address": "Osoite", "Homepage": "Kotisivu", "Year": "vuosi", + "Year_Plural": "vuotta", "Copyright": "Tekijänoikeus © {1}", "DomainDrivenDesign": "Domain Driven Design", "CrossCuttingConcerns": "Cross Cutting Concerns", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json index 21eb33ac23..b9ccddecf4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hu.json @@ -138,6 +138,7 @@ "Address": "Cím", "Homepage": "Kezdőlap", "Year": "Év", + "Year_Plural": "Év", "Copyright": "Copyright © {1}", "DomainDrivenDesign": "Domainvezérelt tervezés", "CrossCuttingConcerns": "Cross Cutting Concerns", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json index 4275bb3771..c57cd13b7f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json @@ -132,6 +132,7 @@ "Address": "Adres", "Homepage": "Anasayfa", "Year": "Yıl", + "Year_Plural": "Yıl", "Copyright": "Telif hakkı © {1}", "DomainDrivenDesign": "Alan Odaklı Tasarım", "CrossCuttingConcerns": "Cross Cutting Concerns", 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 89f156c473..d1a0db7176 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/zh-Hans.json @@ -140,6 +140,7 @@ "Address": "地址", "Homepage": "主页", "Year": "年份", + "Year_Plural": "年份", "Copyright": "版权所有 © {1}", "DomainDrivenDesign": "领域驱动设计", "CrossCuttingConcerns": "横切关注点", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index bddb51b98b..7fda9cc849 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -208,7 +208,7 @@ "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.", "TrialPlan": "Do you have a trial plan?", - "TrialPlanExplanation": "No, there is no trial version for ABP Commercial. You can check the community edition to understand the code quality and approaches. We also offer a 30-day money-back guarantee for the Team license, no questions asked! You can request a refund within the first 30 days. 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@abp.io. 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?", @@ -231,7 +231,7 @@ "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.", "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 refunds are not available for the Business and Enterprise (along with any licenses that include a right to receive source-code). 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 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.", "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", @@ -842,6 +842,63 @@ "BlazoriseSupportExplanation3": "Log into the Blazorise support website at blazorise.com/support/login.", "BlazoriseSupportExplanation4": "If you have an active ABP Commercial license, you will also have a Blazorise PRO license. You can get your Blazorise license key at blazorise.com/support/user/manage/license.", "BlazoriseSupportExplanation5": "You can post your questions on the support website and generate a product token for your application.", - "AbpLiveTrainingPackages": "ABP Live Training Packages" + "AbpLiveTrainingPackages": "ABP Live Training Packages", + "Releases": "Releases", + "ReleasesDescription": "Release logs of ABP Commercial.", + "ReleaseDate": "Release Date", + "Labels": "Labels", + "PreRelease": "Pre-release", + "AllTypes": "All Types", + "Enhancement": "Enhancement", + "Bug": "Bug", + "Feature": "Feature", + "AllUIs": "All UIs", + "MVC": "MVC", + "BlazorServer": "Blazor Server", + "MAUI": "MAUI", + "HowItWorks_Page_Title": "How it works?", + "HowItWorks_Page_Description": "ABP Framework extends the .NET platform. So, anything you can do with a plain .NET solution is already possible with the ABP Framework. That makes it easy to get started with a low learning curve.", + "HowItWorks_Description1": "ABP Framework extends the .NET platform. So, anything you can do with a plain .NET solution is already possible with the ABP Framework. That makes it easy to get started with a low learning curve.", + "HowItWorks_Description2": "Once you start learning and using the ABP Framework features, developing your software will be much more enjoyable than ever.", + "HowItWorks_Description3": "This page basically explains how you use the ABP.IO Platform as a .NET developer.", + "CreateANewSolution": "Create a New .NET Solution", + "CreateANewSolution_Description1": "Everything starts by creating a new ABP integrated .NET solution.", + "StartWithStartupTemplates": "Start one of the pre-built startup solution templates", + "SimpleMonolithApplicationTemplate": "Simple monolith application template", + "LayeredApplicationTemplate": "Layered application template", + "MicroserviceSolutionTemplate": "Microservice solution template", + "CreateEmptySolutionAndUseAbp": "Or create a new empty .NET solution and install ABP NuGet & NPM packages yourself.", + "CreatingSolutionWithMultipleOptions": "There are multiple User Interface and Database options while creating a new solution.", + "UIFrameworkOptions": "UI Framework Options", + "DotnetSolutionWithoutDependency": "Now, you have a regular .NET solution in your local computer that has no dependency to a cloud platform or external service.", + "CheckTheDocumentForDetails": "You can check the {1} document for details.", + "UIAndDatabaseIndependent": "ABP can work with any UI and any database provider supported by .NET. \n However, these UI and database providers are pre-integrated and well documented.", + "InstallAbpModules": "Install ABP Modules", + "DevelopYourSolution": "Develop Your Solution", + "DeployAnywhere": "Deploy Anywhere", + "InstallAbpModule_Description1": "ABP is a modular application development framework. Startup solution templates already come with the essential modules installed. \n But there are more application modules you may want to use in your solution.", + "InstallAbpModule_Description2": "Every module consists of a few NuGet and NPM packages and has an installation document. ABP Suite does most of the work automatically, then you manually configure or fine tune the module based on its documentation.", + "DevelopYourSolution_Description1": "ABP’s infrastructure makes you focus on your own business code by automating the repetitive work and providing pre-built infrastructure and application features.", + "DevelopYourSolution_Description2": "In the following code block, you can see how the ABP Framework seamlessly integrates into your code and automates the repetitive tasks for you.", + "DevelopYourSolution_Description3": "Even in this short code block, ABP does a lot of things for you.", + "DevelopYourSolution_Description4": "It provides base classes to apply conventions, like \n dependency injection. Generic \n repository services provide a convenient \n way to interact with the database. Declarative \n authorization works with a fine-tuned permission system.", + "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.", + "ExpertiseAbpFramework": "Expertise the ABP Framework", + "ExpertiseAbpFramework_Description1": "Want to go beyond basics and get expertise with the ABP.IO Platform?", + "FreeDownload": "Free Download", + "Read": "Read", + "HavingTrouble": "Having Trouble?", + "HavingTrouble_Description1": "Do you have problems with developing your solution? We are here! Use the ABP Support platform \n or send an email to get help directly from the Core ABP Framework team members.", + "WeAreHereToHelp_Description1": "You can browse our help topics or search in the frequently asked questions, \n or you can ask us a question by using the contact form.", + "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", + "ReleaseLogs": "Release Logs", + "ReleaseLogs_Tag": "{0} Release Logs", + "ReleaseLogs_Pr": "Pull Request #{0} - {1}" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json index 9623aba8bf..a6dd050a65 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json @@ -21,7 +21,7 @@ "SetDefault": "Varsayılan olarak ayarla", "DefaultOrganization": "Varsayılan", "StartDate": "Başlangıç tarihi", - "EndDate": "bitiş tarihi", + "EndDate": "Bitiş tarihi", "Modules": "Modüller", "LicenseExtendMessage": "Lisans bitiş tarihiniz {0} tarihine kadar uzatıldı", "LicenseUpgradeMessage": "Lisansınız {0} lisansa yükseltildi", @@ -129,8 +129,8 @@ "TellUsWhatYouNeed": "Bize neye ihtiyacın olduğunu söyle.", "YourMessage": "Mesajın", "YourFullName": "Tam adınız", - "EmailField": "E", - "YourEmailAddress": "E", + "EmailField": "E-posta Adresi", + "YourEmailAddress": "E-posta adresiniz", "HowMayWeHelpYou": "Size nasıl yardımcı olabiliriz?", "SendMessage": "Mesaj gönder", "Success": "Başarı", @@ -808,4 +808,4 @@ "BlazoriseLicense": "Blazorise lisansı satın almamız gerekiyor mu?", "BlazoriseLicenseExplanation": "Volosoft ve Megabit arasında bir anlaşmamız var, bu anlaşma ile Blazorise lisansı ABP Ticari ürünleri ile birlikte geliyor, bu nedenle müşterilerimizin ekstra bir Blazorise lisansı satın almasına gerek kalmıyor." } -} \ No newline at end of file +} diff --git a/common.props b/common.props index e034286ee6..90cd1a1b4a 100644 --- a/common.props +++ b/common.props @@ -1,17 +1,22 @@ latest - 7.3.0 + 7.4.0 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ LGPL-3.0-only git https://github.com/abpframework/abp/ + NuGet.md + aspnetcore boilerplate framework web best-practices angular maui blazor mvc csharp webapp true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + all diff --git a/delete-bin-obj.ps1 b/delete-bin-obj.ps1 index e26ba4fdce..6a6741b767 100644 --- a/delete-bin-obj.ps1 +++ b/delete-bin-obj.ps1 @@ -1,7 +1,5 @@ Clear-Host - Write-Host "Deleting all BIN and OBJ folders..." -ForegroundColor Cyan - Get-ChildItem -Path . -Include bin,obj -Recurse -Directory | ForEach-Object { if ($_.FullName -notmatch "\\node_modules\\") { Write-Host "Deleting:" $_.FullName -ForegroundColor Yellow diff --git a/docs/en/API/Auto-API-Controllers.md b/docs/en/API/Auto-API-Controllers.md index e139c16d20..1644e2d28a 100644 --- a/docs/en/API/Auto-API-Controllers.md +++ b/docs/en/API/Auto-API-Controllers.md @@ -169,3 +169,48 @@ public class PersonAppService : ApplicationService ```` Disabled `IsMetadataEnabled` which hides this service from API explorer and it will not be discoverable. However, it still can be usable for the clients know the exact API path/route. + +## Replace or Remove Controllers. + +In addition to [Overriding a Controller](../Customizing-Application-Modules-Overriding-Services.md#example-overriding-a-controller), you can also use a completely independent **Controller** to replace the controller in the framework or module. + +They have the same [route](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-7.0), but can have **different** input and output parameters. + +### Replace built-in AbpApplicationConfigurationController + +The `ReplaceControllersAttribute` indicates the replaced controller type. + +````csharp +[ReplaceControllers(typeof(AbpApplicationConfigurationController))] +[Area("abp")] +[RemoteService(Name = "abp")] +public class ReplaceBuiltInController : AbpController +{ + [HttpGet("api/abp/application-configuration")] + public virtual Task GetAsync(MyApplicationConfigurationRequestOptions options) + { + return Task.FromResult(new MyApplicationConfigurationDto()); + } +} + +public class MyApplicationConfigurationRequestOptions : ApplicationConfigurationRequestOptions +{ + +} + +public class MyApplicationConfigurationDto : ApplicationConfigurationDto +{ + +} +```` + +### Remove contoller + +Configure `ControllersToRemove` of `AbpAspNetCoreMvcOptions` to remove the controllers. + +````csharp +services.Configure(options => +{ + options.ControllersToRemove.Add(typeof(AbpLanguagesController)); +}); +```` diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md index 3ece459842..de9b87e206 100644 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ b/docs/en/API/Dynamic-CSharp-API-Clients.md @@ -38,20 +38,15 @@ Implement this class in your service application. You can use [auto API controll ## Client Proxy Generation -First, add [Volo.Abp.Http.Client](https://www.nuget.org/packages/Volo.Abp.Http.Client) nuget package to your client project: +> The startup templates already comes pre-configured for the client proxy generation, in the `HttpApi.Client` project. + +If you're not using a startup template, then execute the following command in the folder that contains the .csproj file of your client project: ```` -Install-Package Volo.Abp.Http.Client +abp add-package Volo.Abp.Http.Client ```` -Then add `AbpHttpClientModule` dependency to your module: - -````csharp -[DependsOn(typeof(AbpHttpClientModule))] //add the dependency -public class MyClientAppModule : AbpModule -{ -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Http.Client). Now, it's ready to create the client proxies. Example: @@ -74,8 +69,6 @@ public class MyClientAppModule : AbpModule `AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. -> The startup templates already comes pre-configured for the client proxy generation, in the `HttpApi.Client` project. - ### Endpoint Configuration `RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. The simplest configuration is shown below: diff --git a/docs/en/API/Swagger-Integration.md b/docs/en/API/Swagger-Integration.md index 40660eaab9..d9afd0e2e2 100644 --- a/docs/en/API/Swagger-Integration.md +++ b/docs/en/API/Swagger-Integration.md @@ -21,6 +21,8 @@ Open a command line window in the folder of the `Web` or `HttpApi.Host` project abp add-package Volo.Abp.Swashbuckle ``` +> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Swashbuckle). + ### Manual Installation If you want to manually install; diff --git a/docs/en/Application-Startup.md b/docs/en/Application-Startup.md index 2839fcb19c..5711d61dc4 100644 --- a/docs/en/Application-Startup.md +++ b/docs/en/Application-Startup.md @@ -18,12 +18,14 @@ If you [create a new console application with Visual Studio](https://learn.micro This example uses the [top level statements](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements), so it consists of only a single line of code. -The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the [Package Manager Console](https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-powershell) in Visual Studio: +The first step is to install the [Volo.Abp.Core](https://www.nuget.org/packages/Volo.Abp.Core) NuGet package, which is the most core NuGet package of the ABP framework. You can install it using the ABP CLI. Execute the following command in the folder of the .csproj file that you want to install the package on: ````bash -Install-Package Volo.Abp.Core +abp add-package Volo.Abp.Core ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Core). + Alternatively, you can use a command-line terminal in the root folder of the project (the folder containing the `MyConsoleDemo.csproj` file, for this example): ````bash diff --git a/docs/en/Autofac-Integration.md b/docs/en/Autofac-Integration.md index d05412c94b..43da50299b 100644 --- a/docs/en/Autofac-Integration.md +++ b/docs/en/Autofac-Integration.md @@ -6,27 +6,14 @@ > All the [startup templates](Startup-Templates/Index.md) and samples are Autofac integrated. So, most of the time you don't need to manually install this package. -Install [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) nuget package to your project (for a multi-projects application, it's suggested to add to the executable/web project). +If you're not using a startup template, you can use the [ABP CLI](CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project (suggested to add it to the executable/web project): +````bash +abp add-package Volo.Abp.Autofac ```` -Install-Package Volo.Abp.Autofac -```` - -Then add `AbpAutofacModule` dependency to your module: -```csharp -using Volo.Abp.Modularity; -using Volo.Abp.Autofac; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAutofacModule))] - public class MyModule : AbpModule - { - //... - } -} -``` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Autofac). +> Finally, configure `AbpApplicationCreationOptions` to replace default dependency injection services by Autofac. It depends on the application type. diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 1b04be560f..77f2da8bd0 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -16,6 +16,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.BackgroundJobs.HangFire ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.HangFire). + ### Manual Installation If you want to manually install; diff --git a/docs/en/Background-Jobs-Quartz.md b/docs/en/Background-Jobs-Quartz.md index 97327b5e8a..4c1441da7f 100644 --- a/docs/en/Background-Jobs-Quartz.md +++ b/docs/en/Background-Jobs-Quartz.md @@ -16,6 +16,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.BackgroundJobs.Quartz ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.Quartz). + ### Manual Installation If you want to manually install; diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md new file mode 100644 index 0000000000..ef5a8323c4 --- /dev/null +++ b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md @@ -0,0 +1,267 @@ +# ABP.IO Platform 7.3 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.3 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. + +Try this version and provide feedback for a more stable version of ABP v7.3! Thanks to all of you. + +## Get Started with the 7.3 RC + +Follow the steps below to try version 7.3.0 RC today: + +1) **Upgrade** the ABP CLI to version `7.3.0-rc.1` using a command line terminal: + +````bash +dotnet tool update Volo.Abp.Cli -g --version 7.3.0-rc.1 +```` + +**or install** it if you haven't before: + +````bash +dotnet tool install Volo.Abp.Cli -g --version 7.3.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.2: + +* [ABP Framework 7.2 to 7.3 Migration Guide](https://docs.abp.io/en/abp/7.3/Migration-Guides/Abp-7_3) + +> If you are using the CMS Kit or CMS Kit Pro module, please don't forget to create a new migration and apply it to your database. + +## What's New with ABP Framework 7.3? + +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: + +* Introducing the Volo.Abp.Imaging packages +* ABP CLI: switch-to-local command +* Monitoring Distributed Events +* Ordering of the Local Event Handlers +* Nonce attribute support for Content Security Policy (CSP) +* Other News + +### Introducing the Volo.Abp.Imaging packages + +ABP Framework provides some packages to compress and resize images. Currently, there are four official packages: + +* `Volo.Abp.Imaging.Abstractions`: Provides common services for compression and resizing purposes. +* `Volo.Abp.Imaging.AspNetCore`: Provides some attributes for controller actions that can automatically compress and/or resize uploaded files. +* `Volo.Abp.Imaging.ImageSharp`: Implements the image compression & resize operations using the [ImageSharp](https://github.com/SixLabors/ImageSharp) library. +* `Volo.Abp.Imaging.MagickNet`: Implements the image compression & resize operations using the [Magick.NET](https://github.com/dlemstra/Magick.NET) library. + +You can use one of these official providers (`ImageSharp` or `Magick.NET`) or implement your own image resizer/compressor contributor and use it in your application. + +> See the [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation) documentation to learn more and see the required configurations. + +### ABP CLI: switch-to-local command + +In this version, ABP CLI introduces a new CLI command: **"switch-to-local"**. The `switch-to-local` command changes all NuGet package references on a solution to local project references for all the `.csproj` files in the specified folder (and all its subfolders with any depth). + +**Usage:** + +```bash +abp switch-to-local --paths "C:\Github\abp" +``` + +### Monitoring Distributed Events + +ABP Framework allows you to stay informed when your application **receives** or **sends** a distributed event. This enables you to track the event flow within your application and take appropriate actions based on the received or sent distributed events. + +You just need to subscribe to one of the `DistributedEventReceived` or `DistributedEventSent` events and take additional actions according to your cases. + +**Example: Get informed when your application sends an event to the distributed event bus** + +```csharp +public class DistributedEventSentHandler : ILocalEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(DistributedEventSent eventData) + { + // TODO: IMPLEMENT YOUR LOGIC... + } +} +``` + +> See the documentation to learn more: [https://docs.abp.io/en/abp/7.3/Distributed-Event-Bus](https://docs.abp.io/en/abp/7.3/Distributed-Event-Bus) + +### Ordering of the Local Event Handlers + +In this version, ABP Framework introduces the `LocalEventHandlerOrder` attribute, which can be used to set the execution order for the event handlers. This can be helpful if you want to handle your local event handlers in a specific order. + +**Example:** + +```csharp +[LocalEventHandlerOrder(-1)] +public class MyHandler + : ILocalEventHandler, + ITransientDependency +{ + public async Task HandleEventAsync(StockCountChangedEvent eventData) + { + //TODO: your implementation + } +} +``` + +By default, all event handlers have an order value of 0. Thus, if you want to take certain event handlers to be executed before other event handlers, you can set the order value as a negative value. + +> See the documentation to learn more: [https://docs.abp.io/en/abp/7.3/Local-Event-Bus](https://docs.abp.io/en/abp/7.3/Local-Event-Bus) + +### Nonce attribute support for Content Security Policy (CSP) + +ABP Framework supports adding unique value to nonce attribute for script tags which can be used by Content Security Policy to determine whether or not a given fetch will be allowed to proceed for a given element. In other words, it provides a mechanism to execute only correct script tags with the correct nonce value. + +This feature is disabled by default. You can enable it by setting the *UseContentSecurityPolicyScriptNonce* property of the `AbpSecurityHeadersOptions` class to **true**: + +```csharp +Configure(options => +{ + //adding script-src nonce + options.UseContentSecurityPolicyScriptNonce = true; //false by default +}); +``` + +> See the [Security Headers](https://docs.abp.io/en/abp/7.3/UI/AspNetCore/Security-Headers) documentation for more information. + +### Other News + +* Upgraded the [Blazorise](https://blazorise.com/) library to v1.2.3 for Blazor UI. After the upgrade, ensure that all Blazorise-related packages are using v1.2.3 in your application. +* Module Entity Extension support has been added for the CMS Kit module. See [#16572](https://github.com/abpframework/abp/issues/16572) for more information. + +If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/5.3.0-rc.1), which contains a list of all the issues and pull requests were closed with this version. + +## What's New with ABP Commercial 7.3? + +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 a few new features coming with ABP Commercial 7.3. + +### Account Module - Using Authenticator App for Two-Factor Authentication + +In this version, ABP Commercial provides a new **Two-Factor Authentication (2FA) provider** that allows you to log in to an application by scanning a QR Code with an Authenticator App, such as Microsoft Authenticator or Google Authenticator. + +You need to apply the following actions to configure an Authenticator and then you are free to log in by using the Authenticator App: + +**Step 1 - Enable Two Factor Authentication and Scan the QR Code:** + +![](./two-factor-auth-1.png) + +**Step 2 - Verify the QR Code with an authenticator app:** + +![](./two-factor-auth-2.png) + +**Step 3 - Save the recovery codes for later use in case of not being able to login by verifying the QR code:** + +![](./two-factor-auth-3.png) + +You can disable the two-factor authentication and reset the Authenticator App anytime you want, just by disabling the two-factor authentication or resetting the authenticator: + +![](./reset-authenticator.png) + +### Upgrade Blazorise to v1.2.3 + +Upgraded the [Blazorise](https://blazorise.com/) library to v1.2.3 for Blazor UI. If you are upgrading your project to v7.3.0, please ensure that all the Blazorise-related packages are using v1.2.3 in your application. Otherwise, you might get errors due to incompatible versions. + +### CMS Kit: Module Entity Extensions + +Module entity extension system is a high-level extension system that allows you to define new properties for existing entities of the dependent modules. ABP Framework and ABP Commercial use this system to allow developers to extend entities in different modules. + +In this version, Module Entity Extension support has been added for the CMS Kit Pro module. + +You can open the `YourProjectNameModuleExtensionConfigurator` class inside the `Domain.Shared` project of your solution and change the `ConfigureExtraProperties` method as shown below to add a new property to the `Poll` entity of the [CMS Kit Pro module](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index): + +```csharp +public static void ConfigureExtraProperties() +{ + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance.Modules() + .ConfigureCmsKitPro(cmsKitPro => + { + cmsKitPro.ConfigurePoll(poll => + { + poll.AddOrUpdateProperty( + "", + property => + { + //configuration for this property + } + ) + }); + }); + }); +} + +``` + +> See the [Module Entity Extensions documentation](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) to learn more. + +### LeptonX Account Layout + +In this version, Account Layout has been re-designed for LeptonX Theme. You can see the new account layout in the following figure: + +![](leptonx-account-layout.png) + +> To use this new account layout, ensure that your LeptonX Theme package versions are v2.3+. + +## Community News + +### ABP Community Talks 2023.4: Angular 16 and ABP v7.3 + +![](./community-talks-2023-4.png) + +In this episode, the core ABP team talked about what's new with ABP v7.3 and Angular 16. You can watch the event from [here](https://www.youtube.com/watch?v=lq6u4vQURcI). + +### ABP .NET Conference 2023 + +![](./abp-conf.png) + +We organized ABP .NET Conference 2023 on May 2023 and we are happy to share the success of the conference, which captivated overwhelmingly interested live viewers from all over the world. 13 great line up of speakers which includes .NET experts and Microsoft MVPs delivered captivating talks that resonated with the audiences. Each of the talks attracted a great amount of interest and a lot of questions, sparking curiosity in the attendees. + +Thanks to all speakers and attendees for joining our event. + +> We shared our takeaways in a blog post, which you can read at [https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up](https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up). + +### Volosoft Attendeed & Sponsored Devnot .NET Conference 2023 + +![](devnot-conference.png) + +We are thrilled to announce that the Volosoft Company proudly attended as one of the Gold Sponsors at the Devnot .NET Conference 2023! We are happy to join and be a sponsor of events and contribute to the software society, empowering developers and driving innovation with the .NET community. + +![](devnot-talk.png) + +Co-Founder of [Volosoft](https://volosoft.com/) and Lead Developer of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) gave a word about "Dealing with Concurrency and Multi Threading in .NET" at this event. + +> You can check [this blog post](https://volosoft.com/blog/Reflecting-on-Devnot-Dotnet-Conference-2023) if you want to learn more about the Devnot .NET Conference 2023. + +### New ABP Community Posts + +There are exciting articles contributed by the ABP community as always. I will highlight some of them here: + +* [Authority Delegation in ABP Commercial](https://community.abp.io/posts/authority-delegation-in-abp-commercial-3wtljpp0) by [Liang Shiwei](https://github.com/realLiangshiwei) +* [What's new in Angular 16? New Features and Updates](https://community.abp.io/posts/whats-new-in-angular-16-new-features-and-updates-s1izi9br) by [Masum Ulu](https://twitter.com/masumulu) +* [Kubernetes Integrated Microservice Development with ABP Studio](https://community.abp.io/videos/kubernetes-integrated-microservice-development-with-abp-studio-oix9zkp8) by [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) + +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. + +### New ABP Blog Posts + +There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles: + +* [ABP .NET Conference 2023 Wrap Up](https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up) by [Bige Beşikçi](https://twitter.com/bigedediki) +* [Meet Volosoft at the Devnot .NET Conference 2023!](https://volosoft.com/blog/Meet-Volosoft-at-the-Devnot-.NET-Conference-2023) by [Roo Xu](https://github.com/Roo1227) + +## 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.3/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.3 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-06-05 v7_3_Preview/abp-conf.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png new file mode 100644 index 0000000000..b3add3ebc0 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png new file mode 100644 index 0000000000..7c4bef3950 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png new file mode 100644 index 0000000000..235642a3f8 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png new file mode 100644 index 0000000000..9175452037 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png new file mode 100644 index 0000000000..896663ae28 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png new file mode 100644 index 0000000000..9395011060 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png new file mode 100644 index 0000000000..42651b9479 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png new file mode 100644 index 0000000000..00c9e12456 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png new file mode 100644 index 0000000000..4ccfbcee83 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png differ diff --git a/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png new file mode 100644 index 0000000000..82a58bb479 Binary files /dev/null and b/docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png differ diff --git a/docs/en/CLI-New-Command-Samples.md b/docs/en/CLI-New-Command-Samples.md index d2c63cfec2..635390c29e 100644 --- a/docs/en/CLI-New-Command-Samples.md +++ b/docs/en/CLI-New-Command-Samples.md @@ -73,9 +73,9 @@ The following commands are for creating MVC UI projects: _Note that Public Website is only included in PRO templates._ -## Blazor +## Blazor WebAssembly -The following commands are for creating Blazor projects: +The following commands are for creating Blazor WASM projects: * **Entity Framework Core**, no mobile app: diff --git a/docs/en/CLI.md b/docs/en/CLI.md index a65f29a324..48858edc74 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -419,7 +419,7 @@ abp remove-proxy -t csharp --folder MyProxies/InnerFolder ### switch-to-preview -You can use this command to switch your project to latest preview version of the ABP framework. +You can use this command to switch your solution or project to latest preview version of the ABP framework. Usage: @@ -429,12 +429,12 @@ abp switch-to-preview [options] #### Options -* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. +* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-nightly -You can use this command to switch your project to latest [nightly](Nightly-Builds.md) preview version of the ABP framework packages. +You can use this command to switch your solution or project to latest [nightly](Nightly-Builds.md) preview version of the ABP framework packages. Usage: @@ -444,7 +444,7 @@ abp switch-to-nightly [options] #### Options -* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. +* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-stable @@ -457,7 +457,7 @@ abp switch-to-stable [options] ```` #### Options -* `--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. +* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-local diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/01-microservice-solution.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/01-microservice-solution.png new file mode 100644 index 0000000000..4fb7429ec7 Binary files /dev/null and b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/01-microservice-solution.png differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/02-run-single-service.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/02-run-single-service.png new file mode 100644 index 0000000000..9380eb830f Binary files /dev/null and b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/02-run-single-service.png differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/03-tye-yaml-file.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/03-tye-yaml-file.png new file mode 100644 index 0000000000..5b38d6fccf Binary files /dev/null and b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/03-tye-yaml-file.png differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/04-tye-dashboard.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/04-tye-dashboard.png new file mode 100644 index 0000000000..1444b2a0c6 Binary files /dev/null and b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/04-tye-dashboard.png differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/05-example-microservice-solution.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/05-example-microservice-solution.png new file mode 100644 index 0000000000..663b55a8dc Binary files /dev/null and b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/05-example-microservice-solution.png differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/06-abp-studio-kubernetes-tunnel-how-it-works.png b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/06-abp-studio-kubernetes-tunnel-how-it-works.png new file mode 100644 index 0000000000..de4c124343 Binary files /dev/null and b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/06-abp-studio-kubernetes-tunnel-how-it-works.png differ diff --git a/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md new file mode 100644 index 0000000000..58849266eb --- /dev/null +++ b/docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md @@ -0,0 +1,226 @@ +# Kubernetes Integrated Microservice Development with .NET and ABP Studio + +## Abstract + +**Microservice architecture** is a quite common approach to build **highly scalable** solutions with a large development team. While there are standard tools, like **Kubernetes**, to deploy, run and scale your microservices, the **development side** is not as mature as that. It is not easy to develop, run and test a single microservice that **depends on** other microservices and services. **Running** a copy of the entire system in the developer's machine is impractical. + +In this article, I will introduce an **efficient** way of creating a **development environment** that is well **integrated to Kubernetes**. In this way, you can just run the microservice you are building and let Kubernetes run all your dependencies in your local machine or a remote server. + +I will demonstrate the solution by introducing and using a brand new tool for ABP Developers: **ABP Studio**! + +>**This article is based on my *[Kubernetes Integrated Microservice Development with ABP Studio](https://www.youtube.com/watch?v=XiPRcIHJ3NE)* talk performed in [ABP Dotnet Conf'23](https://abp.io/conference). You can watch that talk if you would like watching rather than reading.** + +## Topics + +Let’s start by introducing the main topics I will cover in this article: + +* I will begin by defining the **problem**: Why it is hard to prepare a development environment for a microservice development. +* I will mention and demonstrate how **Microsoft’s Tye project** tries to solve the problem and why it falls short. +* Then I will introduce the **ABP Studio**, a brand new product for .NET developers, which has an efficient solution for **running multi-application solutions** locally. +* Finally, I will offer an efficient way of developing such complex solutions locally by **integrating Kubernetes into our development environment**, and demonstrate how ABP Studio automates it for us. + +## Difficulties of a Microservice Development Environment + +It is important to understand what is the essential problem when we try to set up a development environment for a microservice solution. To understand the main difficulty, let’s see what a microservice solution looks like: + +![example-microservice-solution](01-microservice-solution.png) + +A typical microservice solution consists of many components communicating to each other. In this example; + +* We have **two applications** for users. One for back-office admin users and one for the end-users of the system, a public website. +* These applications use dedicated **API Gateways** since we are using the Backend For Frontend pattern here. +* There is a separate **authentication service** application to login and manage user accounts. +* Behind the API gateways, we typically have many API applications, called **microservices**. Here, I show only three microservices, but there will be tens of microservices even in a medium-size system. Each of these microservices may have their own databases. +* The microservices typically **interact** with each other using REST API calls or asynchronous messaging through an event bus service, like RabbitMQ here. + +There will be much more **supporting services**, like Redis, Elasticsearch and so on. As you see, we have a lot of independent services and applications interacting with each other in a typical microservice solution. + +### How to run a single service locally? + +Generally, your responsibility is developing **one or a few of these microservices** or applications, not all of them. For example, you may be responsible for developing or testing the Product microservice: + +![run-single-service-in-a-microservice-solution](02-run-single-service.png) + +All you want to do is to write your code, then run and test your service if it is correctly working. However, to be able to **develop**, **run**, **test** or **debug** the product microservice, all of the service and infrastructure dependencies need to be running and communicating to each other. + +Okay, maybe not all, but most of them are needed. Otherwise, you can not open the application UI, login to the system, add products to basket and place an order to see if it is working properly, or you can not **live debug** your service code in case of trouble. + +So, how to make the whole system up and running easily to be able to **focus on developing**, testing and debugging the Product microservice? + +## The Project Tye + +You are not the only one getting trouble to develop, run, test and debug a microservice solution. Microsoft also thought of it and introduced a tool called the [Project Tye](https://github.com/dotnet/tye). With its own words in their GitHub repository: + +> Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. + +Tye can **simplify microservices development** by making it easy to run many services with one command, use dependencies in containers, discover addresses of other services using simple conventions. It can also deploy .NET applications to Kubernetes by automatically creating containers for .NET applications and generating Kubernetes manifests with minimal knowledge or configuration. + +The deployment part is not mature and they have to do a lot of work. However, the solution runner part works very well. + +Tye project consists of three major components: + +* A **YAML file** to configure your services +* A **CLI tool** to run and deploy the solution +* And a **dashboard** to visualize the running services. + +### The tye.yaml file + +Tye uses a YAML file to configure your solution. It typically contains many service definitions. There are three type of services can be defined in this YAML file: + +* You can add a **.NET Project**, so Tye runs it locally with **dotnet run** +* You can add a **container definition**, and Tye runs it using **Docker** +* And an arbitrary **executable application** that can be run using a terminal command + +Here, an example content of a `tye.yaml` file: + +![tye-yaml-file](03-tye-yaml-file.png) + +### The Tye CLI + +Once you define your YAML file, Tye can run all the services with a single terminal command: **tye run**. It builds all .NET applications before running them. If you specify the **watch parameter**, it watches the changes in your .NET projects, re-builds and runs them when a change happens. There are also [other commands](https://github.com/dotnet/tye/blob/main/docs/reference/commandline/README.md) to build and deploy your solution. + +### The Tye Dashboard + +The final major component is the Tye Dashboard. With this dashboard, you can see the **list of the services** with their statuses. You can also view their **logs** and open their **UI** with a single click. + +A screenshot from the Tye Dashboard: + +![project-tye-dashboard](04-tye-dashboard.png) + +### The Example Microservice Solution + +I’ve prepared a microservice solution for demos in this article and I will use the same solution in all demos. Here, an overall diagram of the example solution: + +![example-microservice-solution](05-example-microservice-solution.png) + +We have two web applications, two API Gateways, 5 microservices. 3 of them are shown here: Product, Ordering and Identity microservices. Every microservice has its independent SQL Server database. They communicate through REST API calls and distributed events via RabbitMQ. + +### The Project Tye Demo + +You can watch my 6-minutes demo to see the Project Tye in action: + + + +[Click here to watch the demo on YouTube](https://www.youtube.com/watch?v=S0-z29lMokA) + +### Tye: Shortcomings + +The demo shows how the Tye project is useful. However, there are many missing points and problems with the current project state. Let’s talk about them; + +* The first problem is that it **starts very slow**, because every time you run it, it builds all the services. It takes more than 1 minute for a solution with only ten services. +* It currently can run everything in your **local computer**. Even if you want to develop a single service, running tens of services locally consumes your system resources. You need a lot of RAM and CPU if you want to work a large solution locally. +* The **UI part** is currently very simple. For example, if you want to debug one of your services by running it in Visual Studio, you need to stop all the services, change the YAML file to exclude that service from Tye, then run all the services again. Only after that you can run your service in Visual Studio to debug it as integrated to other services. Obviously this process is not easy and comfortable. +* We can only see the application logs in the UI. **More insights** about the internals of services would be very helpful. +* Tye currently has some **partial documents** in its GitHub repository. However, the documentation is far away from being complete. +* Finally, and most importantly, there is **no active development** on the Tye project on GitHub. It was already an experimental project with its early state, as declared in its GitHub repository. + +So, basically, it seems Microsoft has no further interest in this project and you can expect it will be retired in the near future. + +## ABP Studio + +Okay, now we came to the ABP Studio part. At [Volosoft](https://www.volosoft.com/), we were silently working on the ABP Studio project for more than one year. + +ABP Studio is a **cross-platform desktop application** for ABP developers. It is **well integrated** to the ABP Framework and aims to provide a comfortable development environment for you by **automating things**, **providing insights** about your solution, making **develop**, **run** and **deploy** your solutions much easier. + +We are planning to release a **beta version** in **Q3 of 2023**. You can expect the following features shipping with the initial release: + +* You can **create new ABP solutions** and modules easily with a lot of options. +* You can easily **install or uninstall modules** to your solution. +* You can **explore** the fundamental **structures** of your solution or used modules, like entities, repositories, application services, UI pages, HTTP APIs, database tables and much more. +* You can build **multi-module monolith applications** or distributed microservice solutions by easily adding modules and services into your solution. +* You can easily **run multiple applications** and services with a single click, just like I demonstrated with the Project Tye. However, it has much more features. +* You can run your service by **integrating** it into a **Kubernetes** cluster, so you don’t need to run all your dependencies and all other services in your local development environment. This is the essential topic of this talk and I will later demonstrate how it works. + +## ABP Studio Solution Runner + +I've prepared a demo of the ABP Studio Solution Runner, but first I want to mention the main features of the solution runner: + +* First of all, it can run one, **multiple** or **all services** with a single click. In this way, it is very easy to stop a service, run it in Visual Studio to **test** or **debug**. +* All the services are **connected to ABP Studio** and send their internal data to **visualize** on the ABP Studio UI. +* In the **overall view**, you can see a list of services, view **real-time** HTTP Request and exception counts for each service. +* You can see all details of all **HTTP requests** coming to any service. +* You can see **exception details** as real-time in any service, easily filter and search. +* Just like Tye, you can see the **application logs**. But as more, you can filter logs by log level or search in the log texts. +* Last but not least, ABP Studio has an **integrated Chrome browser** inside it. You can browse the UI of your application without leaving the solution runner. + +### ABP Studio Solution Runner Demo + +You can watch my 9-minutes demo to see ABP Studio Solution Runner in action: + + + +[Click here to watch the demo on YouTube](https://www.youtube.com/watch?v=sSCxyccoHqE) + +## ABP Studio Kubernetes Tunnel + +Finally, we came to the essential topic I want to talk about. Until that point, we had a good understanding of the problem and possible solutions. In this part, we will make a final touch to the solution to have **a great development environment for a microservice solution**. + +ABP Studio Kubernetes Tunnel System allows you to **connect your local development environment to a local or remote Kubernetes cluster**, where that cluster already runs your microservice solution. In this way, you can access any service in Kubernetes with their service name as DNS, just like they are running in your local computer. This is established with a secure VPN connection. + +Secondly, you can **intercept any service** in that cluster, so all the **traffic to the intercepted service is automatically redirected to your service** that is running in your local machine. When your service needs to use any service in Kubernetes, the traffic is redirected back to the cluster, just like your local service is running inside the Kubernetes. In this way, you don’t need to care about how all other services are configured and running. You just focus on the service you are responsible for developing, testing or debugging. You can use your favorite IDE since you are running your service in your local machine as you always do. + +The solution runner is a great way of running multiple services locally. However, if your solution consists of hundreds of services, running all them in your local machine will consume your system resources and slow down your development speed. The best news is that: You can use the **Kubernetes Tunnelling combined with all the solution runner features** to have a perfect local microservice development environment. + +### How ABP Studio Kubernetes Tunnel works + +I am sure that you want to see it in action, but before that, let me explain how the solution works. + +![abp-studio-kubernetes-tunnel-how-it-works](06-abp-studio-kubernetes-tunnel-how-it-works.png) + +* **Kubernetes cluster** is shown on the right side and your **local development machine** is shown on the left side. +* As you know, when a **user requests a web page** from your web application, the request is accepted by an **Ingress Controller** inside your Kubernetes cluster. +* The Ingress controller forwards the request to your **web application**, which then uses an **API gateway** to consume your microservices. +* Assume that we have the **Product**, **Ordering** and **Identity** microservices. +* These microservices have their own SQL Server **database** and they are also using **RabbitMQ**, **Redis** and some other **infrastructure services**. +* On the other hand, we want to develop, run or test our **Product microservice** in our **own laptop** using our favorite IDE, let’s say using Visual Studio. +* If we don’t make anything special, the **product microservice in the Kubernetes cluster** will be used by the users, as you can expect. Even if you can somehow run the product microservice in your local computer, the Kubernetes system won’t have any knowledge about it. They are in different systems. +* At this point, ABP Studio comes into play. When you **connect** to the Kubernetes cluster with ABP Studio, it first installs a **VPN server** into your cluster and a **VPN client** into your local machine. +* Then it establishes a secure **VPN tunnel** between your computer and the target Kubernetes cluster. In this way, you can access all services in Kubernetes with their internal cluster IP addresses. +* ABP Studio also installs the **ABP Studio Client Proxy Server** pod into the Kubernetes cluster, and the **ABP Studio Proxy Client** into the developer machine. It is used to collect data from the services and show them in the ABP Studio UI, like HTTP Requests, exceptions, or logs as we’ve seen in the solution runner before. +* Finally, ABP Studio also adds **DNS records to your hosts file**, so you can access to internal Kubernetes services directly with their service names, in addition to their IP addresses. + +All of these happen when you click the Connect button on the ABP Studio. Now, your computer can use the **internals of the Kubernetes cluster**. But, how the HTTP Requests coming to the Product service in the Kubernetes cluster are **redirected to your local machine**? + +* When you want to run and test a service locally, you **intercept the matched service** in the Kubernetes cluster. When you Intercept a service, ABP Studio makes some more changes in your local computer and in your Kubernetes cluster. +* As first, it installs a **pod** and a **service** into the Kubernetes cluster to intercept the requests coming to the Product microservice and **proxies the requests to** a client application in **your local computer**. +* The local client application then **forwards requests** to the Product microservice instance you are running in your local machine. In this way, whenever the Product service is used in Kubernetes, your local product service is executed. You can easily run, test or debug your service as you normally do with your standard IDE, for example with Visual Studio. +* What happens when your local product service needs to **access** the SQL Server database or RabbitMQ, or another microservice in the Kubernetes cluster? It can **directly use** them just like local services, through the **VPN tunnel** we have already created. +* ABP Studio makes a final change in your local product microservice project: It overrides all the **environment variables** with the ones obtained from the product service in the Kubernetes pod. In this way, your local product service instance feels itself in the Kubernetes cluster. Database connection strings and all other settings will be the same with the Kubernetes environment. + +Don’t worry, ABP Studio **doesn’t change your local environment variables**. The change only affects the live product service instance, and it is **reverted** when you disable the interception in ABP Studio. Also, when you click the **Disconnect** button in ABP Studio, all the changes made to your cluster and your local computer will be cleared. It won’t leave any sign in your systems. + +### ABP Studio Kubernetes Tunnel Demo + +So, now we have a fully Kubernetes integrated development environment. Let’s see how it is used in ABP Studio in the following 9-minutes demo: + + + +[Click here to watch the demo on YouTube](https://www.youtube.com/watch?v=CeUq2ysz-mQ) + +## ABP Studio Roadmap + +In the next months, the team will be working on the following items: + +* Our first goal is to **finalize and stabilize** the current features. We will also **document** how to use them. Only after that, we will open it to customers as a beta version. +* We’ve focused on the development part until now. We also want to add some **deployment** capabilities to ABP Studio in the future. +* Another goal is to make **module customizations** much easier than current by the help of ABP Studio. With ABP Studio, we have completely re-though how to discover and install modules. Based on that, we will also work on a common place to publish and consume application modules for the ABP Framework. + +We have other great plans for that product. However, I can not declare them yet as they are secrets for now :) We’ve planned to release the first beta version in **Q3 of 2023**. + +I want to notice that ABP Studio is not a new version or replacement of **ABP Suite**. We built the ABP Studio from scratch. Both products will be developed and shipped in parallel. ABP Suite will continue to focus on code generation. ABP Studio doesn’t have any code generation capabilities yet. So, you will use both of them together. + +## Summary + +Okay let’s summarize what I’ve explained in this article: + +* First, I explained **why it is hard to set up a microservice development environment**. We’ve talked about the hardness of developing, running and testing a single microservice that depends on infrastructure services and other microservices. +* I’ve introduced **Project Tye**, a project developed by Microsoft to allow you to easily run multi-application solutions. We made a demo, then discussed its missing points. +* Then I **introduced ABP Studio**, a new product by Volosoft for ABP developers. + As the first important feature, I explained the ABP Studio **Solution Runner** and demonstrated how it works and how you can develop and debug your services. +* Finally, I introduced ABP Studio’s **Kubernetes Tunneling** feature. First, I explained how it works with a complicated diagram. Then I made a short demo to see it in action. + +With that final part, we had a **great and easy to use microservice development environment**. It is great, because we can focus on only a single service, while we don’t care about all other parts of the solution. Also, we can save our local development machine system resources by delegating running the dependencies to a remote Kubernetes server. + +## See Also + +* ABP Dotnet Conf'23 Talk on YouTube: [Kubernetes Integrated Microservice Development with ABP Studio](https://www.youtube.com/watch?v=XiPRcIHJ3NE) \ No newline at end of file diff --git a/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md b/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md index 9b3225a989..719caf844f 100644 --- a/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md +++ b/docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md @@ -55,4 +55,4 @@ Your backend should be running successfully There is a demo app. The path of the demo app is `npm\ng-packs\apps\dev-app`. The demo app is connected to the packages with local references. Open the terminal in `npm\ng-packs\apps\dev-app` and execute `yarn` or `npm i` in terminal. After the package installed run `npm start` or `yarn start`. -The repo uses Nx and packages connected with `local references`. The packages path is `”npm\ng-packs\packages` +The repo uses Nx and packages connected with `local references`. The packages path is `npm\ng-packs\packages` diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index f1b8464d59..550ea6d9f8 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -185,6 +185,51 @@ If you perform **database operations** and use the [repositories](Repositories.m > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. +## Monitoring Distributed Events + +The ABP Framework allows you to stay informed when your application **receives** or **sends** a distributed event. This capability enables you to track the event flow within your application and take appropriate actions based on the received or sent distributed events. + +### Received Events + +The `DistributedEventReceived` local event is published when your application receives an event from the distributed event bus. `DistributedEventReceived` class has the following fields: + +- **`Source`:** It represents the source of the distributed event. Source can be `Direct`, `Inbox`, `Outbox`. +- **`EventName`:** It represents the [name](#event-name) of the event received. +- **`EventData`:** It represents the actual data associated with the event received. Since it is of type `object`, it can hold any type of data. + +**Example: Get informed when your application receives an event from the distributed event bus** + +```csharp +public class DistributedEventReceivedHandler : ILocalEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(DistributedEventReceived eventData) + { + // TODO: IMPLEMENT YOUR LOGIC... + } +} +``` + +### Sent Events + +The `DistributedEventSent` local event is published when your application sends an event to the distributed event bus. `DistributedEventSent` class has the following fields: + +- **`Source`:** It represents the source of the distributed event. Source can be `Direct`, `Inbox`, `Outbox`. +- **`EventName`:** It represents the [name](#event-name) of the event sent. +- **`EventData`:** It represents the actual data associated with the event sent. Since it is of type `object`, it can hold any type of data. + +**Example: Get informed when your application sends an event to the distributed event bus** + +```csharp +public class DistributedEventSentHandler : ILocalEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(DistributedEventSent eventData) + { + // TODO: IMPLEMENT YOUR LOGIC... + } +} +``` + +You can seamlessly integrate event-tracking capabilities into your application by subscribing to the `DistributedEventReceived` and `DistributedEventSent` local events as above examples. This empowers you to effectively monitor the messaging flow, diagnose any potential issues, and gain valuable insights into the behavior of your distributed messaging system. ## Pre-Defined Events diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index fc17ddcd49..e2cfd21a05 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -7,24 +7,11 @@ This document explains how to integrate EF Core as an ORM provider to ABP based `Volo.Abp.EntityFrameworkCore` is the main NuGet package for the EF Core integration. Install it to your project (for a layered application, to your data/infrastructure layer): ```` shell -Install-Package Volo.Abp.EntityFrameworkCore +abp add-package Volo.Abp.EntityFrameworkCore ```` -Then add `AbpEntityFrameworkCoreModule` module dependency (`DependsOn` attribute) to your [module](Module-Development-Basics.md): - -````c# -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.Modularity; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpEntityFrameworkCoreModule))] - public class MyModule : AbpModule - { - //... - } -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore). +> > Note: Instead, you can directly download a [startup template](https://abp.io/Templates) with EF Core pre-installed. diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md index 117b0b499e..0a6424ac3b 100644 --- a/docs/en/Getting-Started-AspNetCore-Application.md +++ b/docs/en/Getting-Started-AspNetCore-Application.md @@ -18,11 +18,13 @@ This tutorial explains how to start ABP from scratch with minimal dependencies. ## Install Volo.Abp.AspNetCore.Mvc Package -Volo.Abp.AspNetCore.Mvc is the AspNet Core MVC integration package for ABP. So, install it on your project: +You can use the [ABP CLI](CLI.md) to install the Volo.Abp.AspNetCore.Mvc package to your project. Execute the following command in the folder of the .csproj file that you want to install the package on: +````bash +abp add-package Volo.Abp.AspNetCore.Mvc ```` -Install-Package Volo.Abp.AspNetCore.Mvc -```` + +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc). ## Create the First ABP Module diff --git a/docs/en/Getting-Started-React-Native.md b/docs/en/Getting-Started-React-Native.md index bc3c7ef419..ba55af5acb 100644 --- a/docs/en/Getting-Started-React-Native.md +++ b/docs/en/Getting-Started-React-Native.md @@ -36,17 +36,9 @@ abp new MyCompanyName.MyProjectName -csf -u -m react-native This command will prepare a solution with an **Angular** or an **MVC** (depends on your choice), a **.NET Core**, and a **React Native** project in it. -### 2. Direct Download +### 2. Generating a CLI Command from Get Started Page -You may [download a solution scaffold directly on ABP.io](https://abp.io/get-started) if you are more comfortable with GUI or simply want to try ABP without installing the CLI. - -Please do the following: - -1. Click on the "DIRECT DOWNLOAD" tab. -2. Fill out the short form about your project. -3. Click on the "Create now" button. - -...and a customized download will start in a few seconds. +You can generate a CLI command on the [get started page of the abp.io website](https://abp.io/get-started). Then, use the command on your terminal to create a new [Startup Template](./Startup-Templates/Index.md). ## How to Configure & Run the Backend @@ -54,26 +46,6 @@ Please do the following: > When you are using OpenIddict, You should remove 'clientSecret' on Environment.js (if exists) and disable "HTTPS-only" settings. (Openiddict has default since Version 6.0) -### How to disable Https-only in Openiddict. - -You should add this code on {{ if Tiered == "No" }}`MyProjectNameHttpApiHostModule`{{ else if Tiered == "Yes" }}`MyProjectNameAuthServerModule`{{ end }}. - -```csharp - public override void PreConfigureServices(ServiceConfigurationContext context) - { - #if DEBUG - PreConfigure(options => - { - options - .UseAspNetCore() - .DisableTransportSecurityRequirement(); - }); - #endif - //.... - } - -``` - A React Native application running on an Android emulator or a physical phone **can not connect to the backend** on `localhost`. To fix this problem, it is necessary to run the backend application on your **local IP address**. {{ if Tiered == "No"}} @@ -99,13 +71,14 @@ Run the backend application as described in the [getting started document](Getti ## How to disable the Https-only settings of OpenIddict -Go to MyProjectNameHttpApiHostModule.cs under the host project. And put these codes under the `PreConfigureServices` function. +Open the {{ if Tiered == "No" }}`MyProjectNameHttpApiHostModule`{{ else if Tiered == "Yes" }}`MyProjectNameAuthServerModule`{{ end }} project and copy-paste the below code-block to the `PreConfigureServices` method: ```csharp #if DEBUG - PreConfigure(options => { - options.UseAspNetCore() - .DisableTransportSecurityRequirement(); + PreConfigure(options => + { + options.UseAspNetCore() + .DisableTransportSecurityRequirement(); }); #endif ``` diff --git a/docs/en/Image-Manipulation.md b/docs/en/Image-Manipulation.md index 7aff8cc1df..1050b6005e 100644 --- a/docs/en/Image-Manipulation.md +++ b/docs/en/Image-Manipulation.md @@ -41,7 +41,7 @@ public class YourModule : AbpModule ABP Framework provides two image resizer/compressor implementations out of the box: -* [Magick.NET](#magicknet-provider) +* [Magick.NET](#magick-net-provider) * [ImageSharp](#imagesharp-provider) You should install one of these provides to make it actually working. @@ -377,4 +377,4 @@ public async Task Upload(IFormFile file) { //... } -``` \ No newline at end of file +``` diff --git a/docs/en/Integration-Services.md b/docs/en/Integration-Services.md index e788c348f7..98e3e593e5 100644 --- a/docs/en/Integration-Services.md +++ b/docs/en/Integration-Services.md @@ -34,11 +34,47 @@ public interface IProductAppService : IApplicationService That's all. From now, ABP will handle your application service as integration service and implement the followings by convention: -* If you are using the [Auto API Controllers](API/Auto-API-Controllers.md) feature in your application, the URL prefix will be `/integration-api` instead of `/api` for your integration services. Thus, you can distinguish internal and external service communications and take additional actions, such as preventing REST API calls for integration services out of API Gateway. -* Audit logging is disabled by default for the integration services. See the next section if you want to enable it. +* That service is **not exposed** by default, unless you explicitly set `ExposeIntegrationServices` options (see the *Exposing Integration Services* section). +* If you are using the [Auto API Controllers](API/Auto-API-Controllers.md) feature in your application, the **URL prefix** will be `/integration-api` instead of `/api` for your integration services. Thus, you can distinguish internal and external service communications and take additional actions, such as preventing REST API calls for integration services out of API Gateway. +* **Audit logging** is disabled by default for the integration services. See the next section if you want to enable it. + +## Marking an MVC Controller as Integration Service + +In addition to application services, you can mark a regular MVC Controller as integration service, using the same `IntegrationService` attribute, or inheriting an interface that has the `IntegrationService` attribute. + +**Example:** + +````csharp +[IntegrationService] // Mark as integration service +[Route("integration-api/products")] +public class ProductController : AbpControllerBase +{ + //... +} +```` + +When you use the `IntegrationService` attribute, ABP will handle your controller as integration service and implement the followings by convention: + +* That controller is **not exposed** to clients by default, unless you explicitly set `ExposeIntegrationServices` options (see the *Exposing Integration Services* section). +* **Audit logging** is disabled by default for controller. See the next section if you want to enable it. ## Configuration +### Exposing Integration Services + +Integration services and controllers are not exposed by default for security reasons. They typically don't require authorization, so you should **carefully and explicitly** allow them to be visible and usable to client applications. + +To expose integration services and controllers, set `AbpAspNetCoreMvcOptions.ExposeIntegrationServices` to `true` in the `ConfigureServices` method of your [module class](Module-Development-Basics.md): + +````csharp +Configure(options => +{ + options.ExposeIntegrationServices = true; +}); +```` + +> Hiding integration services is useful when you are building reusable application modules, where they may be used in a monolith application or in a microservice system. In a monolith application, integration services don't need to be exposed outside since the modules may in-process communicate with each other. On the other hand, if you build a microservice solution and use that module as a service, it will be proper to expose the integration services, so other microservices can consume them remotely inside your private network (or Kubernetes cluster). In that case, be careful to not accidently expose the integration services out of your private network. Configuring your API Gateway so that it blocks requests to `integration-api` prefixed URLs from outside of your network will be a good option. + ### Enabling/Disabling the Audit Logging Audit Logging is disabled by default for integration services but it can be enabled by configuring the `AbpAuditingOptions` [options class](Options.md) in the `ConfigureServices` method of your [module class](Module-Development-Basics.md): diff --git a/docs/en/Localization.md b/docs/en/Localization.md index a432d6afb2..4fec386c69 100644 --- a/docs/en/Localization.md +++ b/docs/en/Localization.md @@ -6,11 +6,13 @@ ABP's localization system is seamlessly integrated to the `Microsoft.Extensions. > This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. -Volo.Abp.Localization is the core package of the localization system. Install it to your project using the package manager console (PMC): +You can use the [ABP CLI](CLI.md) to install the Volo.Abp.Localization package to your project. Execute the following command in the folder of the .csproj file that you want to install the package on: +```bash +abp add-package Volo.Abp.Localization ``` -Install-Package Volo.Abp.Localization -``` + +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Localization). Then you can add **AbpLocalizationModule** dependency to your module: diff --git a/docs/en/Migration-Guides/Abp-7_3.md b/docs/en/Migration-Guides/Abp-7_3.md new file mode 100644 index 0000000000..9e09306ca3 --- /dev/null +++ b/docs/en/Migration-Guides/Abp-7_3.md @@ -0,0 +1,28 @@ +# ABP Version 7.3 Migration Guide + +This document is a guide for upgrading ABP v7.2 solutions to ABP v7.3. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. + +## OpenIddict - Refactoring of `ClaimsPrincipal` + +There are some changes that you might need to fix in your code. You can see the following list of the required changes: + +* `AbpOpenIddictClaimDestinationsManager` was renamed as `AbpOpenIddictClaimsPrincipalManager`. +* Use `AbpOpenIddictClaimsPrincipalManager.HandleAsync` instead of `AbpOpenIddictClaimDestinationsManager.SetAsync`, which is removed. +* `AbpDefaultOpenIddictClaimDestinationsProvider` was renamed as `AbpDefaultOpenIddictClaimsPrincipalHandler`. +* `IAbpOpenIddictClaimDestinationsProvider` was renamed as `IAbpOpenIddictClaimsPrincipalHandler`. +* Use `IAbpOpenIddictClaimsPrincipalHandler.HandleAsync` instead of `IAbpOpenIddictClaimDestinationsProvider.SetAsync`, which is removed. +* `AbpOpenIddictClaimDestinationsOptions` was renamed as `AbpOpenIddictClaimsPrincipalOptions`. + +Please check [this PR](https://github.com/abpframework/abp/pull/16537) if you encounter any problems related to OpenIddict Module. + +## Nonce attribute support for Content Security Policy (CSP) + +ABP Framework supports adding unique value to nonce attribute for script tags which can be used by Content Security Policy to determine whether or not a given fetch will be allowed to proceed for a given element. In other words, it provides a mechanism to execute only correct script tags with the correct nonce value. + +> See the [Security Headers](../UI/AspNetCore/Security-Headers.md) documentation for more information. + +This feature comes with a small restriction. If you use any C# code used inside the script tag, it may cause errors (Because a new `NonceScriptTagHelper` has been added, and it replaces script tags in the HTML contents). + +For example, `` will no longer work. However, you can use the C# code for an attribute of script tag, for example, `` is completely valid and won't cause any problem. + +> Note: You should not use any C# code used inside the script tag, even if you don't use this feature. Because it might cause errors. \ No newline at end of file diff --git a/docs/en/Migration-Guides/Index.md b/docs/en/Migration-Guides/Index.md index 7d85d3f46f..c87004cfb5 100644 --- a/docs/en/Migration-Guides/Index.md +++ b/docs/en/Migration-Guides/Index.md @@ -2,6 +2,7 @@ The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../Upgrading.md). +- [7.2 to 7.3](Abp-7_3.md) - [7.1 to 7.2](Abp-7_2.md) - [7.0 to 7.1](Abp-7_1.md) - [6.0 to 7.0](Abp-7_0.md) diff --git a/docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md b/docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md new file mode 100644 index 0000000000..5ec8eb6c65 --- /dev/null +++ b/docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md @@ -0,0 +1,8 @@ +# ABP Version 7.4 Migration Guide + +This document is a guide for upgrading ABP v7.3 solutions to ABP v7.4. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. + +## TemplateDefinition + +The `LocalizationResource(Type)` of `TemplateDefinition` class is changed to `LocalizationResourceName(string)`. + diff --git a/docs/en/Module-Entity-Extensions.md b/docs/en/Module-Entity-Extensions.md index 47436a7dd9..93c79120cd 100644 --- a/docs/en/Module-Entity-Extensions.md +++ b/docs/en/Module-Entity-Extensions.md @@ -255,6 +255,20 @@ property => Use `property.UI.OnCreateForm` and `property.UI.OnEditForm` to control forms too. If a property is required, but not added to the create form, you definitely get a validation exception, so use this option carefully. But a required property may not be in the edit form if that's your requirement. +### UI Order + +When you define a property, it appears on the data table, create and edit forms on the related UI page. However, you can control its order. Example: + +````csharp +property => +{ + property.UI.Order = 1; + //...other configurations +} +```` + +Use `property.UI.OnCreateForm` and `property.UI.OnEditForm` to control forms too. If a property is required, but not added to the create form, you definitely get a validation exception, so use this option carefully. But a required property may not be in the edit form if that's your requirement. + ### HTTP API Availability Even if you disable a property on UI, it can be still available through the HTTP API. By default, a property is available on all APIs. diff --git a/docs/en/Modules/Cms-Kit/Blogging.md b/docs/en/Modules/Cms-Kit/Blogging.md index 91c14d79e8..d45598be52 100644 --- a/docs/en/Modules/Cms-Kit/Blogging.md +++ b/docs/en/Modules/Cms-Kit/Blogging.md @@ -118,4 +118,8 @@ This module follows the [Domain Services Best Practices & Conventions](https://d - CmsBlogs - CmsBlogPosts -- CmsBlogFeatures \ No newline at end of file +- CmsBlogFeatures + +## Entity Extensions + +Check the ["Entity Extensions" section of the CMS Kit Module documentation](Index.md#entity-extensions) to see how to extend entities of the Blogging Feature of the CMS Kit module. \ No newline at end of file diff --git a/docs/en/Modules/Cms-Kit/Index.md b/docs/en/Modules/Cms-Kit/Index.md index e40f78922e..952fa87aba 100644 --- a/docs/en/Modules/Cms-Kit/Index.md +++ b/docs/en/Modules/Cms-Kit/Index.md @@ -80,3 +80,66 @@ All tables/collections use the `Cms` prefix by default. Set static properties on This module uses `CmsKit` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. + +## Entity Extensions + +[Module entity extension](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) system is a **high-level** extension system that allows you to **define new properties** for existing entities of the dependent modules. It automatically **adds properties to the entity**, **database**, **HTTP API and user interface** in a single point. + +To extend entities of the CMS Kit Pro module, open your `YourProjectNameModuleExtensionConfigurator` class inside of your `DomainShared` project and change the `ConfigureExtraProperties` method like shown below. + +```csharp +public static void ConfigureExtraProperties() +{ + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance.Modules() + .ConfigureCmsKit(cmsKit => + { + cmsKit.ConfigureBlog(plan => // extend the Blog entity + { + plan.AddOrUpdateProperty( //property type: string + "BlogDescription", //property name + property => { + //validation rules + property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property + + //...other configurations for this property + } + ); + }); + + cmsKit.ConfigureBlogPost(blogPost => // extend the BlogPost entity + { + blogPost.AddOrUpdateProperty( //property type: string + "BlogPostDescription", //property name + property => { + //validation rules + property.Attributes.Add(new RequiredAttribute()); //adds required attribute to the defined property + property.Attributes.Add( + new StringLengthAttribute(MyConsts.MaximumDescriptionLength) { + MinimumLength = MyConsts.MinimumDescriptionLength + } + ); + + //...other configurations for this property + } + ); + }); + }); + }); +} +``` + +* `ConfigureCmsKit(...)` method is used to configure the entities of the CMS Kit module. + +* `cmsKit.ConfigureBlog(...)` is used to configure the **Blog** entity of the CMS Kit module. You can add or update your extra properties of the +**Blog** entity. + +* `cmsKit.ConfigureBlogPost(...)` is used to configure the **BlogPost** entity of the CMS Kit module. You can add or update your extra properties of the **BlogPost** entity. + +* You can also set some validation rules for the property that you defined. In the above sample, `RequiredAttribute` and `StringLengthAttribute` were added for the property named **"BlogPostDescription"**. + +* When you define the new property, it will automatically add to **Entity**, **HTTP API** and **UI** for you. + * Once you define a property, it appears in the create and update forms of the related entity. + * New properties also appear in the datatable of the related page. + diff --git a/docs/en/Modules/Virtual-File-Explorer.md b/docs/en/Modules/Virtual-File-Explorer.md index 1a22418937..a952b2c1db 100644 --- a/docs/en/Modules/Virtual-File-Explorer.md +++ b/docs/en/Modules/Virtual-File-Explorer.md @@ -10,9 +10,13 @@ Virtual File Explorer Module provided a simple UI to view all files in [virtual #### 1- Use ABP CLI -It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: +It is recommended to use the [ABP CLI](../CLI.md) to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command: -`abp add-module Volo.VirtualFileExplorer` +``` +abp add-module Volo.VirtualFileExplorer +``` + +> If you haven't done it yet, you first need to install the [ABP CLI](../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.VirtualFileExplorer.Web). #### 2- Manually install diff --git a/docs/en/MongoDB.md b/docs/en/MongoDB.md index bfdac23104..372ad74ae6 100644 --- a/docs/en/MongoDB.md +++ b/docs/en/MongoDB.md @@ -4,12 +4,14 @@ This document explains how to integrate MongoDB as a database provider to ABP ba ## Installation -`Volo.Abp.MongoDB` is the main nuget package for the MongoDB integration. Install it to your project (for a layered application, to your data/infrastructure layer): +`Volo.Abp.MongoDB` is the main NuGet package for the MongoDB integration. Install it to your project (for a layered application, to your data/infrastructure layer), You can use the [ABP CLI](CLI.md) to install it to your project. Execute the following command in the folder of the .csproj file of the layer: ``` -Install-Package Volo.Abp.MongoDB +abp add-package Volo.Abp.MongoDB ``` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.MongoDB). + Then add `AbpMongoDbModule` module dependency to your [module](Module-Development-Basics.md): ```c# diff --git a/docs/en/PlugIn-Modules.md b/docs/en/PlugIn-Modules.md index cbcc380de2..70f7af67fb 100644 --- a/docs/en/PlugIn-Modules.md +++ b/docs/en/PlugIn-Modules.md @@ -61,11 +61,13 @@ Create a simple **Class Library Project** in a solution: ![simple-plugin-library](images/simple-plugin-library.png) -You can add ABP Framework packages you need to use in the module. At least, you should add the `Volo.Abp.Core` package to the project: +You can add the ABP Framework packages that you need to use in the module. At least, you should add the `Volo.Abp.Core` package to the project, Execute the following command in the folder of the .csproj file that you want to install the package on: +````bash +abp add-package Volo.Abp.Core ```` -Install-Package Volo.Abp.Core -```` + + If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Core). Every [module](Module-Development-Basics.md) must declare a class derived from the `AbpModule`. Here, a simple module class that resolves a service and initializes it on the application startup: diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 23ed2371dd..e39c100b47 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -140,9 +140,9 @@ public async Task> GetListAsync(string filter) Any standard LINQ method can be used over the `IQueryable` returned from the repository. -> This sample uses `ToList()` method, but it is **strongly suggested to use the asynchronous methods** to perform database queries, like `ToListAsync()` for this example. -> -> See the **IQueryable & Async Operations** section to learn how you can do it. +> This sample uses `ToList()` method, but it is **strongly suggested to use the asynchronous methods** to perform database queries, like `ToListAsync()` for this example. See the **`IQueryable` & Async Operations** section to learn how you can do it. + +> **Exposing `IQueryable` to outside of a repository** class may leak your data access logic to the application layer. If you want to strictly follow the **layered architecture** principles, you can consider to implement a custom repository class and wrap your data access logic inside your repository class. You can see the ***Custom Repositories*** section to learn how to create custom repository classes for your application. ### Bulk Operations @@ -192,6 +192,31 @@ Major vendors, like Entity Framework, NHibernate or MongoDB already support `IQu There are also `IReadOnlyRepository` and `IReadOnlyBasicRepository` interfaces for who only want to depend on querying capabilities of the repositories. +The `IReadOnlyRepository` derives the `IReadOnlyBasicRepository` and provides the following properties and methods as well: + +Properties: + +`AsyncExecuter`: a service that is used to execute an `IQueryable` object asynchronously **without depending on the actual database provider**. + +Methods: + +- `GetListAsync()` +- `GetQueryableAsync()` +- `WithDetails()` 1 overload +- `WithDetailsAsync()` 1 overload + + + +Where as the `IReadOnlyBasicRepository` provides the following methods: + +- `GetCountAsync()` +- `GetListAsync()` +- `GetPagedListAsync()` + +They can all be seen as below: + +![generic-repositories](\images\generic-repositories.png) + ### 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/Road-Map.md b/docs/en/Road-Map.md index 8fa7b9b2e7..f8b772dbca 100644 --- a/docs/en/Road-Map.md +++ b/docs/en/Road-Map.md @@ -4,11 +4,11 @@ This document provides a road map, release schedule and planned features for the ## Next Versions -### v7.3 +### v7.4 -The next version will be 7.3 and planned to release the stable 7.3 version in July, 2023. In the version 7.3, we will mostly focus on stabilizing and enhancing existing features, improving the developer experience, as well as adding relatively minor new features. +The next version will be 7.4 and planned to release the stable 7.4 version in September, 2023. In the version 7.4, we will mostly focus on stabilizing and enhancing existing features, improving the developer experience, as well as adding relatively minor new features. -See the [7.3 milestone](https://github.com/abpframework/abp/milestone/82) for all the issues we've planned to work on. +See the [7.4 milestone](https://github.com/abpframework/abp/milestone/85) for all the issues we've planned to work on. ## Backlog Items @@ -28,6 +28,8 @@ Here, a list of major items in the backlog we are considering to work on in the * [#16342](https://github.com/abpframework/abp/issues/16342) / CmsKit: Meta information for SEO * [#16260](https://github.com/abpframework/abp/issues/16260) / GCP Blob Storage Provider * [#15932](https://github.com/abpframework/abp/issues/15932) / Introduce ABP Diagnostics Module +* [#16756](https://github.com/abpframework/abp/issues/16756) / Blob Storing - Provider configuration UI +* [#16744](https://github.com/abpframework/abp/issues/16744) / State Management API You can always check the milestone planning and the prioritized backlog issues on [the GitHub repository](https://github.com/abpframework/abp/milestones) for a detailed road map. The backlog items are subject to change. We are adding new items and changing priorities based on the community feedbacks and goals of the project. diff --git a/docs/en/SMS-Sending.md b/docs/en/SMS-Sending.md index efe63c3c91..3043ce643a 100644 --- a/docs/en/SMS-Sending.md +++ b/docs/en/SMS-Sending.md @@ -17,6 +17,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.Sms ``` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.Sms). + ### Manual Installation If you want to manually install; diff --git a/docs/en/SignalR-Integration.md b/docs/en/SignalR-Integration.md index d532067911..f441c64ae1 100644 --- a/docs/en/SignalR-Integration.md +++ b/docs/en/SignalR-Integration.md @@ -18,6 +18,8 @@ abp add-package Volo.Abp.AspNetCore.SignalR > You typically want to add this package to the web or API layer of your application, depending on your architecture. +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.SignalR). + #### Manual Installation If you want to manually install; diff --git a/docs/en/Text-Templating-Razor.md b/docs/en/Text-Templating-Razor.md index 44a207a991..bf911abd4a 100644 --- a/docs/en/Text-Templating-Razor.md +++ b/docs/en/Text-Templating-Razor.md @@ -16,6 +16,8 @@ Open a command line window in the folder of the project (.csproj file) and type abp add-package Volo.Abp.TextTemplating.Razor ```` +> If you haven't done it yet, you first need to install the [ABP CLI](CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.TextTemplating.Razor). + ### Manual Installation If you want to manually install; diff --git a/docs/en/UI/Angular/Authorization.md b/docs/en/UI/Angular/Authorization.md index 1bde885180..448a83e0cd 100644 --- a/docs/en/UI/Angular/Authorization.md +++ b/docs/en/UI/Angular/Authorization.md @@ -32,7 +32,7 @@ According to this flow, the user is redirected to an external login page which i ### Resource Owner Password Flow -If you implemented the [Angular UI account module](./Account-Module) to your project, you can switch the flow to resource owner password flow by changing the OAuth configuration in the _environment.ts_ files as shown below: +If you have used the [Angular UI account module](./Account-Module) in your project, you can switch to the resource owner password flow by changing the OAuth configuration in the _environment.ts_ files as shown below: ```js import { Config } from '@abp/ng.core'; diff --git a/docs/en/UI/AspNetCore/Bundling-Minification.md b/docs/en/UI/AspNetCore/Bundling-Minification.md index d262a9dd7f..dd2282c883 100644 --- a/docs/en/UI/AspNetCore/Bundling-Minification.md +++ b/docs/en/UI/AspNetCore/Bundling-Minification.md @@ -12,27 +12,13 @@ ABP offers a simple, dynamic, powerful, modular and built-in way. > This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. -Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: +If you're not using a startup template, you can use the [ABP CLI](../../CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project: ```` -install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling +abp add-package Volo.Abp.AspNetCore.Mvc.UI.Bundling ```` -Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] - public class MyWebModule : AbpModule - { - //... - } -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](../../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc.UI.Bundling). ## Razor Bundling Tag Helpers @@ -308,28 +294,13 @@ Using the built-in contributors for standard packages; > This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. -Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. -To install it to your project: +If you're not using a startup template, you can use the [ABP CLI](../../CLI.md) to install it to your project. Execute the following command in the folder that contains the .csproj file of your project: ```` -install-package Volo.Abp.AspNetCore.Mvc.UI.Packages +abp add-package Volo.Abp.AspNetCore.Mvc.UI.Packages ```` -Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpAspNetCoreMvcUiPackagesModule))] - public class MyWebModule : AbpModule - { - //... - } -} -```` +> If you haven't done it yet, you first need to install the [ABP CLI](../../CLI.md). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.AspNetCore.Mvc.UI.Packages). ### Bundle Inheritance diff --git a/docs/en/UI/AspNetCore/Security-Headers.md b/docs/en/UI/AspNetCore/Security-Headers.md index b02e9691b0..dad20089a6 100644 --- a/docs/en/UI/AspNetCore/Security-Headers.md +++ b/docs/en/UI/AspNetCore/Security-Headers.md @@ -74,3 +74,26 @@ Configure(options => }); }); ``` + +### Ignore Abp Security Headers + +You can ignore the Abp Security Headers for some actions or pages. You can use the `IgnoreAbpSecurityHeaderAttribute` attribute for this. + +**Example:** + +```csharp +@using Volo.Abp.AspNetCore.Security +@attribute [IgnoreAbpSecurityHeaderAttribute] +``` + +**Example:** + +```csharp +[IgnoreAbpSecurityHeaderAttribute] +public class IndexModel : AbpPageModel +{ + public void OnGet() + { + } +} +``` \ No newline at end of file diff --git a/docs/en/images/generic-repositories.png b/docs/en/images/generic-repositories.png new file mode 100644 index 0000000000..4a162569a9 Binary files /dev/null and b/docs/en/images/generic-repositories.png differ diff --git a/framework/NuGet.md b/framework/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/framework/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 1cf8068de2..a475aa8aa5 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -439,6 +439,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Ldap.Abstractions" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Ddd.Domain.Shared", "src\Volo.Abp.Ddd.Domain.Shared\Volo.Abp.Ddd.Domain.Shared.csproj", "{0858571B-CE73-4AD6-BD06-EC9F0714D8E9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MultiTenancy.Abstractions", "src\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj", "{86F3684C-A0A5-4943-8CFA-AE79E8E3E315}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.Abstractions", "src\Volo.Abp.Imaging.Abstractions\Volo.Abp.Imaging.Abstractions.csproj", "{32F3E84B-D02E-42BD-BC5C-0D211564EF30}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Imaging.AspNetCore", "src\Volo.Abp.Imaging.AspNetCore\Volo.Abp.Imaging.AspNetCore.csproj", "{78340A37-219E-4F2D-9AC6-40A7B467EEEC}" @@ -1325,6 +1327,10 @@ Global {0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Release|Any CPU.ActiveCfg = Release|Any CPU {0858571B-CE73-4AD6-BD06-EC9F0714D8E9}.Release|Any CPU.Build.0 = Release|Any CPU + {86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86F3684C-A0A5-4943-8CFA-AE79E8E3E315}.Release|Any CPU.Build.0 = Release|Any CPU {32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Debug|Any CPU.Build.0 = Debug|Any CPU {32F3E84B-D02E-42BD-BC5C-0D211564EF30}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1578,6 +1584,7 @@ Global {8764DFAF-D13D-449A-9A5E-5D7F0B2D7FEF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {0F80E95C-41E6-4F23-94FF-FC9D0B8D5D71} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {0858571B-CE73-4AD6-BD06-EC9F0714D8E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {86F3684C-A0A5-4943-8CFA-AE79E8E3E315} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {32F3E84B-D02E-42BD-BC5C-0D211564EF30} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {44467427-E0BE-492C-B9B4-82B362C183C3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {F701EDA5-D7EA-4AA7-9C57-83ED50CE72EC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs index ea73a5673f..5811cc22c1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/StandardLayouts.cs @@ -1,6 +1,11 @@ -namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout; public static class StandardLayouts { public const string Application = "Application"; + public const string Account = "Account"; + public const string Public = "Public"; + public const string Empty = "Empty"; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs new file mode 100644 index 0000000000..6bba37e0f8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/AbpThemingOptions.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class AbpThemingOptions +{ + public ThemeDictionary Themes { get; } + + public string DefaultThemeName { get; set; } + + public AbpThemingOptions() + { + Themes = new ThemeDictionary(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs new file mode 100644 index 0000000000..c85a25bf19 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeManager.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class DefaultThemeManager : IThemeManager, IScopedDependency, IServiceProviderAccessor +{ + public IServiceProvider ServiceProvider { get; } + public ITheme CurrentTheme => GetCurrentTheme(); + + private ITheme _currentTheme; + + protected IThemeSelector ThemeSelector { get; } + + public DefaultThemeManager( + IServiceProvider serviceProvider, + IThemeSelector themeSelector) + { + ServiceProvider = serviceProvider; + ThemeSelector = themeSelector; + } + + protected virtual ITheme GetCurrentTheme() + { + if (_currentTheme != null) + { + return _currentTheme; + } + + _currentTheme = (ITheme)ServiceProvider.GetRequiredService(ThemeSelector.GetCurrentThemeInfo().ThemeType); + return CurrentTheme; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs new file mode 100644 index 0000000000..7e08ed1d97 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/DefaultThemeSelector.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class DefaultThemeSelector : IThemeSelector, ITransientDependency +{ + protected AbpThemingOptions Options { get; } + + public DefaultThemeSelector(IOptions options) + { + Options = options.Value; + } + + public virtual ThemeInfo GetCurrentThemeInfo() + { + if (!Options.Themes.Any()) + { + throw new AbpException($"No theme registered! Use {nameof(AbpThemingOptions)} to register themes."); + } + + if (Options.DefaultThemeName == null) + { + return Options.Themes.Values.First(); + } + + var themeInfo = Options.Themes.Values.FirstOrDefault(t => t.Name == Options.DefaultThemeName); + if (themeInfo == null) + { + throw new AbpException("Default theme is configured but it's not found in the registered themes: " + Options.DefaultThemeName); + } + + return themeInfo; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs new file mode 100644 index 0000000000..f379252f52 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ITheme.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface ITheme +{ + Type GetLayout(string name, bool fallbackToDefault = true); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs new file mode 100644 index 0000000000..75bb35e8d8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeManager.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface IThemeManager +{ + ITheme CurrentTheme { get; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs new file mode 100644 index 0000000000..6cf92c4cfa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/IThemeSelector.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public interface IThemeSelector +{ + ThemeInfo GetCurrentThemeInfo(); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs new file mode 100644 index 0000000000..bf33a1f083 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeDictionary.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class ThemeDictionary : Dictionary +{ + public ThemeInfo Add() + where TTheme : ITheme + { + return Add(typeof(TTheme)); + } + + public ThemeInfo Add(Type themeType) + { + if (ContainsKey(themeType)) + { + throw new AbpException("This theme is already added before: " + themeType.AssemblyQualifiedName); + } + + return this[themeType] = new ThemeInfo(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs new file mode 100644 index 0000000000..3236f491fe --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public static class ThemeExtensions +{ + public static Type GetApplicationLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Application, fallbackToDefault); + } + + public static Type GetAccountLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Account, fallbackToDefault); + } + + public static Type GetPublicLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Public, fallbackToDefault); + } + + public static Type GetEmptyLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Empty, fallbackToDefault); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs new file mode 100644 index 0000000000..a17f6c08ba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeInfo.cs @@ -0,0 +1,24 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +public class ThemeInfo +{ + public Type ThemeType { get; } + + public string Name { get; } + + public ThemeInfo([NotNull] Type themeType) + { + Check.NotNull(themeType, nameof(themeType)); + + if (!typeof(ITheme).IsAssignableFrom(themeType)) + { + throw new AbpException($"Given {nameof(themeType)} ({themeType.AssemblyQualifiedName}) should be type of {typeof(ITheme).AssemblyQualifiedName}"); + } + + ThemeType = themeType; + Name = ThemeNameAttribute.GetName(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs new file mode 100644 index 0000000000..fe4e6de371 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Theming/ThemeNameAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Theming; + +[AttributeUsage(AttributeTargets.Class)] +public class ThemeNameAttribute : Attribute +{ + public string Name { get; set; } + + public ThemeNameAttribute(string name) + { + Name = name; + } + + public static string GetName(Type themeType) + { + return themeType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault()?.Name ?? themeType.Name; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj b/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj index 79d67f8b57..95c23d34ad 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components/Volo.Abp.AspNetCore.Components.csproj @@ -18,6 +18,7 @@ + diff --git a/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpAspNetCoreComponentsModule.cs b/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpAspNetCoreComponentsModule.cs index b73432b38a..dd48830a24 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpAspNetCoreComponentsModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components/Volo/Abp/AspNetCore/Components/AbpAspNetCoreComponentsModule.cs @@ -4,6 +4,7 @@ using Volo.Abp.AspNetCore.Components.DependencyInjection; using Volo.Abp.DynamicProxy; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; using Volo.Abp.Security; using Volo.Abp.Timing; @@ -13,7 +14,8 @@ namespace Volo.Abp.AspNetCore.Components; [DependsOn( typeof(AbpObjectMappingModule), typeof(AbpSecurityModule), - typeof(AbpTimingModule) + typeof(AbpTimingModule), + typeof(AbpMultiTenancyAbstractionsModule) )] public class AbpAspNetCoreComponentsModule : AbpModule { 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 da0dad6a70..b1d5bac7fb 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 @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -21,7 +22,6 @@ public class AbpAspNetCoreMvcDaprEventsController : AbpController var daprSerializer = HttpContext.RequestServices.GetRequiredService(); var body = (await JsonDocument.ParseAsync(HttpContext.Request.Body)); - var id = body.RootElement.GetProperty("id").GetString(); var pubSubName = body.RootElement.GetProperty("pubsubname").GetString(); var topic = body.RootElement.GetProperty("topic").GetString(); var data = body.RootElement.GetProperty("data").GetRawText(); @@ -32,8 +32,31 @@ public class AbpAspNetCoreMvcDaprEventsController : AbpController } var distributedEventBus = HttpContext.RequestServices.GetRequiredService(); - var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic)); - await distributedEventBus.TriggerHandlersAsync(id, distributedEventBus.GetEventType(topic), eventData); + + if (IsAbpDaprEventData(data)) + { + var daprEventData = daprSerializer.Deserialize(data, typeof(AbpDaprEventData)).As(); + var eventData = daprSerializer.Deserialize(daprEventData.JsonData, distributedEventBus.GetEventType(daprEventData.Topic)); + await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(daprEventData.Topic), eventData, daprEventData.MessageId, daprEventData.CorrelationId); + } + else + { + var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic)); + await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic), eventData); + } + return Ok(); } + + protected virtual bool IsAbpDaprEventData(string data) + { + var document = JsonDocument.Parse(data); + var objects = document.RootElement.EnumerateObject().ToList(); + return objects.Count == 5 && + objects.Any(x => x.Name.Equals("PubSubName", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("Topic", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("MessageId", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("JsonData", StringComparison.CurrentCultureIgnoreCase)) && + objects.Any(x => x.Name.Equals("CorrelationId", StringComparison.CurrentCultureIgnoreCase)); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelper.cs index 463cd73971..6f2ed964cb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; @@ -21,6 +22,8 @@ public class AbpDynamicFormTagHelper : AbpTagHelper _routeValues; private const string ActionAttributeName = "asp-action"; private const string AreaAttributeName = "asp-area"; @@ -57,7 +60,22 @@ public class AbpDynamicFormTagHelper : AbpTagHelper RouteValues { get; set; } + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _routeValues; + } + set + { + _routeValues = value; + } + } #endregion diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs index b9a72fac0f..5938ad356d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs @@ -100,14 +100,16 @@ public class AbpDynamicFormTagHelperService : AbpTagHelperService"); - foreach (var item in items.OrderBy(o => o.Order)) { contentBuilder.AppendLine(SetColumn(item.HtmlContent)); } - contentBuilder.AppendLine(""); + if (TagHelper.ColumnSize != ColumnSize.Undefined && TagHelper.ColumnSize != ColumnSize._) + { + contentBuilder.Insert(0, "
"); + contentBuilder.AppendLine("
"); + } var content = childContent.GetContent(); if (content.Contains(AbpFormContentPlaceHolder)) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs index 1cd4b88fb8..56acc2f745 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -14,6 +14,8 @@ public class AbpAspNetCoreMvcOptions public HashSet ControllersToRemove { get; } + public bool ExposeIntegrationServices { get; set; } = false; + public bool AutoModelValidation { get; set; } public bool EnableRazorRuntimeCompilationOnDevelopment { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index 933f325975..19479f552d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs @@ -45,6 +45,7 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency protected virtual void ApplyForControllers(ApplicationModel application) { RemoveDuplicateControllers(application); + RemoveIntegrationControllersIfNotExposed(application); foreach (var controller in GetControllers(application)) { @@ -72,6 +73,20 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency } } + protected virtual void RemoveIntegrationControllersIfNotExposed(ApplicationModel application) + { + if (Options.ExposeIntegrationServices) + { + return; + } + + var integrationControllers = GetControllers(application) + .Where(c => IntegrationServiceAttribute.IsDefinedOrInherited(c.ControllerType)) + .ToArray(); + + application.Controllers.RemoveAll(integrationControllers); + } + protected virtual IList GetControllers(ApplicationModel application) { return application.Controllers; diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpAspNetCoreApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpAspNetCoreApplicationBuilderExtensions.cs index 23a73c74c7..684c948975 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpAspNetCoreApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpAspNetCoreApplicationBuilderExtensions.cs @@ -23,7 +23,7 @@ public static class AbpAspNetCoreApplicationBuilderExtensions .GetRequiredService>() .Value; - if (!options.EndpointConfigureActions.Any()) + if (!options.EndpointConfigureActions.Any() && additionalConfigurationAction == null) { return app; } @@ -32,15 +32,18 @@ public static class AbpAspNetCoreApplicationBuilderExtensions { using (var scope = app.ApplicationServices.CreateScope()) { - var context = new EndpointRouteBuilderContext(endpoints, scope.ServiceProvider); - - foreach (var configureAction in options.EndpointConfigureActions) + if (options.EndpointConfigureActions.Any()) { - configureAction(context); - } + var context = new EndpointRouteBuilderContext(endpoints, scope.ServiceProvider); - additionalConfigurationAction?.Invoke(endpoints); + foreach (var configureAction in options.EndpointConfigureActions) + { + configureAction(context); + } + } } + + additionalConfigurationAction?.Invoke(endpoints); }); } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs index c9b0738619..b1068bbe85 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs @@ -33,11 +33,19 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x => x.Contains("text/html") || x.Contains("*/*") || x.Contains("application/xhtml+xml")); + + var endpoint = context.GetEndpoint(); + + if (endpoint?.Metadata.GetMetadata() != null) + { + await next.Invoke(context); + return; + } if (!requestAcceptTypeHtml || !Options.Value.UseContentSecurityPolicyHeader || await AlwaysIgnoreContentTypes(context) - || context.GetEndpoint() == null + || endpoint == null || Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/')))) { AddOtherHeaders(context); @@ -111,7 +119,7 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency var nonceStr = $" 'nonce-{nonceValue}'"; - var scriptSrcValue = Options.Value.ContentSecurityPolicyValue.Split(';') + var scriptSrcValue = Options.Value.ContentSecurityPolicyValue?.Split(';') .FirstOrDefault(x => x.Trim().StartsWith(ScriptSrcKey))?.Trim(); if (scriptSrcValue.IsNullOrWhiteSpace()) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/IgnoreAbpSecurityHeader.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/IgnoreAbpSecurityHeader.cs new file mode 100644 index 0000000000..fb9e7717f3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/IgnoreAbpSecurityHeader.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.AspNetCore.Security; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class IgnoreAbpSecurityHeaderAttribute : Attribute +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs index 23f1192d1c..33f5de3582 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using System; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -20,16 +21,31 @@ public class AbpCorrelationIdMiddleware : IMiddleware, ITransientDependency public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var correlationId = _correlationIdProvider.Get(); + var correlationId = GetCorrelationIdFromRequest(context); - try + using (_correlationIdProvider.Change(correlationId)) { - await next(context); + try + { + await next(context); + } + finally + { + CheckAndSetCorrelationIdOnResponse(context, _options, correlationId); + } } - finally + } + + protected virtual string GetCorrelationIdFromRequest(HttpContext context) + { + string correlationId = context.Request.Headers[_options.HttpHeaderName]; + if (correlationId.IsNullOrEmpty()) { - CheckAndSetCorrelationIdOnResponse(context, _options, correlationId); + correlationId = Guid.NewGuid().ToString("N"); + context.Request.Headers[_options.HttpHeaderName] = correlationId; } + + return correlationId; } protected virtual void CheckAndSetCorrelationIdOnResponse( diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs deleted file mode 100644 index 02e5f91596..0000000000 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Tracing; - -namespace Volo.Abp.AspNetCore.Tracing; - -[Dependency(ReplaceServices = true)] -public class AspNetCoreCorrelationIdProvider : ICorrelationIdProvider, ITransientDependency -{ - protected IHttpContextAccessor HttpContextAccessor { get; } - protected AbpCorrelationIdOptions Options { get; } - - public AspNetCoreCorrelationIdProvider( - IHttpContextAccessor httpContextAccessor, - IOptions options) - { - HttpContextAccessor = httpContextAccessor; - Options = options.Value; - } - - public virtual string Get() - { - if (HttpContextAccessor.HttpContext?.Request?.Headers == null) - { - return CreateNewCorrelationId(); - } - - string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName]; - - if (correlationId.IsNullOrEmpty()) - { - lock (HttpContextAccessor.HttpContext.Request.Headers) - { - if (correlationId.IsNullOrEmpty()) - { - correlationId = CreateNewCorrelationId(); - HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId; - } - } - } - - return correlationId; - } - - protected virtual string CreateNewCorrelationId() - { - return Guid.NewGuid().ToString("N"); - } -} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo.Abp.Authorization.Abstractions.csproj b/framework/src/Volo.Abp.Authorization.Abstractions/Volo.Abp.Authorization.Abstractions.csproj index 86c690687d..86f6a8498a 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo.Abp.Authorization.Abstractions.csproj +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo.Abp.Authorization.Abstractions.csproj @@ -19,7 +19,7 @@
- +
diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/AbpAuthorizationAbstractionsModule.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/AbpAuthorizationAbstractionsModule.cs index 11d6d5a634..e1e40cd205 100644 --- a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/AbpAuthorizationAbstractionsModule.cs +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/AbpAuthorizationAbstractionsModule.cs @@ -4,7 +4,7 @@ using Volo.Abp.MultiTenancy; namespace Volo.Abp.Authorization; [DependsOn( - typeof(AbpMultiTenancyModule) + typeof(AbpMultiTenancyAbstractionsModule) )] public class AbpAuthorizationAbstractionsModule : AbpModule { diff --git a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj index 02dcdde621..81077c16f9 100644 --- a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj +++ b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj @@ -20,11 +20,13 @@
+ - + + diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index cb244599c2..d8226bdee5 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -8,6 +8,7 @@ using Volo.Abp.Authorization.Permissions; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Security; using Volo.Abp.VirtualFileSystem; @@ -16,7 +17,8 @@ namespace Volo.Abp.Authorization; [DependsOn( typeof(AbpAuthorizationAbstractionsModule), typeof(AbpSecurityModule), - typeof(AbpLocalizationModule) + typeof(AbpLocalizationModule), + typeof(AbpMultiTenancyModule) )] public class AbpAuthorizationModule : AbpModule { diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj index 6a7eded3d8..3633fd45e3 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo.Abp.BackgroundJobs.Abstractions.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs index 8d3dc6caf5..4bad5cee72 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Json; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.BackgroundJobs; [DependsOn( - typeof(AbpJsonModule) + typeof(AbpJsonModule), + typeof(AbpMultiTenancyAbstractionsModule) )] public class AbpBackgroundJobsAbstractionsModule : AbpModule { diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo.Abp.BackgroundJobs.csproj b/framework/src/Volo.Abp.BackgroundJobs/Volo.Abp.BackgroundJobs.csproj index a9450e134c..0ef692a9f2 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo.Abp.BackgroundJobs.csproj +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo.Abp.BackgroundJobs.csproj @@ -19,6 +19,7 @@ + diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs index 08cc66130c..793cf99634 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs @@ -6,6 +6,7 @@ using Volo.Abp.Data; using Volo.Abp.DistributedLocking; using Volo.Abp.Guids; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Timing; @@ -16,7 +17,8 @@ namespace Volo.Abp.BackgroundJobs; typeof(AbpBackgroundWorkersModule), typeof(AbpTimingModule), typeof(AbpGuidsModule), - typeof(AbpDistributedLockingAbstractionsModule) + typeof(AbpDistributedLockingAbstractionsModule), + typeof(AbpMultiTenancyModule) )] public class AbpBackgroundJobsModule : AbpModule { diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor index eebff3970d..b9ef1e0f8e 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor @@ -8,7 +8,7 @@ { - + @PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor index 8fa7f0d777..0500d29c32 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor @@ -13,6 +13,7 @@ diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor index 9fe96c7a20..aa546793b9 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor @@ -25,6 +25,7 @@ __builder.AddAttribute(1, "PropertyInfo", propertyInfo); __builder.AddAttribute(2, "Entity", Entity); __builder.AddAttribute(3, "LH", LH); + __builder.AddAttribute(4, "ModalType", ModalType); __builder.CloseComponent(); } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs index 947069a7ee..d9a1a42d85 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending; @@ -16,4 +17,7 @@ public partial class ExtensionProperties : Component [Parameter] public TEntityType Entity { get; set; } + + [Parameter] + public ExtensionPropertyModalType? ModalType { get; set; } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyComponentBase.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyComponentBase.cs index b2a5c1e48d..46f4bd7f51 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyComponentBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyComponentBase.cs @@ -33,6 +33,9 @@ public abstract class ExtensionPropertyComponentBase : O [Parameter] public AbpBlazorMessageLocalizerHelper LH { get; set; } + [Parameter] + public ExtensionPropertyModalType? ModalType { get; set; } + protected virtual void Validate(ValidatorEventArgs e) { e.Status = ValidationStatus.Success; @@ -65,12 +68,14 @@ public abstract class ExtensionPropertyComponentBase : O } e.MemberNames = result.MemberNames; - e.Status = ValidationStatus.Error; + e.Status = ValidationStatus.Error; e.ErrorText = errorMessage; break; } } + protected bool IsReadonlyField => ModalType is ExtensionPropertyModalType.EditModal && PropertyInfo.UI.EditModal.IsReadOnly; + private static string GetDefaultErrorMessage(ValidationAttribute validationAttribute) { if (validationAttribute is StringLengthAttribute stringLengthAttribute && stringLengthAttribute.MinimumLength != 0) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyModalType.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyModalType.cs new file mode 100644 index 0000000000..67cf8d5bb8 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionPropertyModalType.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending; + +public enum ExtensionPropertyModalType : byte +{ + CreateModal = 0, + EditModal = 1 +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor index 2ea468f1f4..7c32549d51 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor @@ -15,6 +15,7 @@ SelectedValueChanged="@SelectedValueChanged" SearchChanged="@SearchFilterChangedAsync" Validator="@Validate" - MinLength="0"> + MinLength="0" + Disabled="IsReadonlyField"> diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor index cc1735c26a..605158aec0 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor @@ -10,7 +10,7 @@ @foreach (var item in SelectItems) { - @item.Text + @item.Text } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor index fc8b7b5398..e9d3d7031e 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor @@ -9,7 +9,7 @@ @PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) - + diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor index 6ad15b2cb5..5d3f1f26af 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor @@ -9,7 +9,7 @@ @PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)--> - + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs index 0d5b3b31e5..66519a2275 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.Cli.Commands; public class LoginCommand : IConsoleCommand, ITransientDependency { public const string Name = "login"; - + public ILogger Logger { get; set; } protected AuthService AuthService { get; } @@ -122,6 +122,12 @@ public class LoginCommand : IConsoleCommand, ITransientDependency return; } + if (ex.Message.Contains("RequiresTwoFactor")) + { + Logger.LogError("Two factor authentication is enabled for your account. Please use `abp login --device` command to login."); + return; + } + if (TryGetErrorMessageFromHtmlPage(ex.Message, out var errorMsg)) { Logger.LogError(errorMsg); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs index 4893680004..fd1fdbab89 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs @@ -31,7 +31,7 @@ public class SwitchToNightlyCommand : IConsoleCommand, ITransientDependency sb.AppendLine(" abp switch-to-nightly [options]"); sb.AppendLine(""); sb.AppendLine("Options:"); - sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine("-d|--directory"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs index 84b58d6b8b..695330267e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs @@ -31,7 +31,7 @@ public class SwitchToPreviewCommand : IConsoleCommand, ITransientDependency sb.AppendLine(" abp switch-to-preview [options]"); sb.AppendLine(""); sb.AppendLine("Options:"); - sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine("-d|--directory"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs index cc0fa9ff75..c5976e2b75 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs @@ -31,7 +31,7 @@ public class SwitchToStableCommand : IConsoleCommand, ITransientDependency sb.AppendLine(" abp switch-to-stable [options]"); sb.AppendLine(""); sb.AppendLine("Options:"); - sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine("-d|--directory"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs index c303c1196a..73e33211e3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs @@ -566,28 +566,37 @@ public class ChangeThemeStep : ProjectBuildPipelineStep private static void ChangeThemeToBasicForMvcProjects(ProjectBuildContext context, string defaultThemeName) { - var projects = new Dictionary - { - {".Web", "MyProjectNameWebModule"}, - {".HttpApi.Host", "MyProjectNameHttpApiHostModule"}, - {".AuthServer", "MyProjectNameAuthServerModule"}, - {".Web.Public", "MyProjectNameWebPublicModule"}, - {".Web.Public.Host", "MyProjectNameWebPublicModule"}, - {"", "MyProjectNameWebModule"} + var projectNames = new[] +{ + ".Web", ".HttpApi.Host", ".AuthServer", ".Web.Public", ".Web.Public.Host", + "" //for app-nolayers-mvc }; - foreach (var project in projects) + foreach (var projectName in projectNames) { + var projectPath = $"/MyCompanyName.MyProjectName{projectName}/MyCompanyName.MyProjectName{projectName}.csproj"; + var projectFile = context.Files.FirstOrDefault(x => x.Name.Contains(projectPath)); + if (projectFile == null) + { + continue; + } + + var moduleFile = ConvertProjectFileToModuleFile(context, projectFile); + if (moduleFile == null) + { + continue; + } + ReplacePackageReferenceWithProjectReference( context, - $"/MyCompanyName.MyProjectName{project.Key}/MyCompanyName.MyProjectName{project.Key}.csproj", + projectFile.Name, $"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}", @"..\..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj" ); - + ReplaceAllKeywords( context, - $"/MyCompanyName.MyProjectName{project.Key}/{project.Value}.cs", + moduleFile.Name, defaultThemeName, Basic ); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs index 124209d559..4127d4b23d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs @@ -19,7 +19,8 @@ public class TemplateCodeDeleteStep : ProjectBuildPipelineStep file.Name.EndsWith(".ps1") || file.Name.EndsWith(".html") || file.Name.EndsWith(".ts") || - file.Name.EndsWith(".css")) + file.Name.EndsWith(".css") || + file.Name.Contains("Dockerfile")) { file.RemoveTemplateCode(context.Symbols); file.RemoveTemplateCodeMarkers(); 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 fec1136204..ca5f5ceb61 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 @@ -26,6 +26,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase { case DatabaseProvider.NotSpecified: case DatabaseProvider.EntityFrameworkCore: + context.Symbols.Add("EFCORE"); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc.Mongo")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host.Mongo")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server.Mongo")); @@ -50,6 +51,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase switch (context.BuildArgs.UiFramework) { case UiFramework.Angular: + context.Symbols.Add("ui:angular"); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); steps.Add(new ProjectRenameStep("MyCompanyName.MyProjectName.Host", "MyCompanyName.MyProjectName")); @@ -65,6 +67,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase break; case UiFramework.Blazor: + context.Symbols.Add("ui:blazor"); steps.Add(new RemoveFolderStep("/angular")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); @@ -83,6 +86,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase break; case UiFramework.BlazorServer: + context.Symbols.Add("ui:blazor-server"); steps.Add(new RemoveFolderStep("/angular")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Mvc")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host")); @@ -92,6 +96,7 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase case UiFramework.NotSpecified: case UiFramework.Mvc: + context.Symbols.Add("ui:mvc"); steps.Add(new RemoveFolderStep("/angular")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Host")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor.Server")); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs index 03fcf65036..9e65595182 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; @@ -32,6 +33,37 @@ public class PackagePreviewSwitcher : ITransientDependency { var solutionPaths = GetSolutionPaths(commandLineArgs); + if (solutionPaths.Any()) + { + await SwitchSolutionsToPreview(solutionPaths); + } + else + { + var projectPaths = GetProjectPaths(commandLineArgs); + + await SwitchProjectsToPreview(projectPaths); + } + } + + private async Task SwitchProjectsToPreview(List projects) + { + foreach (var project in projects) + { + var folder = Path.GetDirectoryName(project); + + await _nugetPackagesVersionUpdater.UpdateProjectAsync( + project, + includeReleaseCandidates: true); + + await _npmPackagesUpdater.Update( + folder, + false, + true); + } + } + + private async Task SwitchSolutionsToPreview(List solutionPaths) + { foreach (var solutionPath in solutionPaths) { var solutionFolder = Path.GetDirectoryName(solutionPath); @@ -56,69 +88,134 @@ public class PackagePreviewSwitcher : ITransientDependency } } - public async Task SwitchToNightlyPreview(CommandLineArgs commandLineArgs) + public async Task SwitchToStable(CommandLineArgs commandLineArgs) { var solutionPaths = GetSolutionPaths(commandLineArgs); + if (solutionPaths.Any()) + { + await SwitchSolutionsToStable(solutionPaths); + } + else + { + var projectPaths = GetProjectPaths(commandLineArgs); + + await SwitchProjectsToStable(projectPaths); + } + } + + private async Task SwitchProjectsToStable(List projects) + { + foreach (var project in projects) + { + var folder = Path.GetDirectoryName(project); + + await _nugetPackagesVersionUpdater.UpdateProjectAsync( + project, + false, + false, + true); + + await _npmPackagesUpdater.Update( + folder, + false, + false, + true); + } + } + + private async Task SwitchSolutionsToStable(List solutionPaths) + { foreach (var solutionPath in solutionPaths) { var solutionFolder = Path.GetDirectoryName(solutionPath); var solutionAngularFolder = GetSolutionAngularFolder(solutionFolder); - _packageSourceManager.Add(solutionFolder, "ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json"); + _packageSourceManager.Remove(solutionFolder, "ABP Nightly"); - if (solutionPath != null) - { - await _nugetPackagesVersionUpdater.UpdateSolutionAsync( - solutionPath, - true); - } + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( + solutionPath, + false, + false, + true); await _npmPackagesUpdater.Update( solutionFolder, + false, + false, true); if (solutionAngularFolder != null) { await _npmPackagesUpdater.Update( solutionAngularFolder, + false, + false, true); } } } - public async Task SwitchToStable(CommandLineArgs commandLineArgs) + public async Task SwitchToNightlyPreview(CommandLineArgs commandLineArgs) { var solutionPaths = GetSolutionPaths(commandLineArgs); + if (solutionPaths.Any()) + { + await SwitchSolutionsToNightlyPreview(solutionPaths); + } + else + { + var projectPaths = GetProjectPaths(commandLineArgs); + + await SwitchProjectsToNightlyPreview(projectPaths); + } + } + + private async Task SwitchProjectsToNightlyPreview(List projects) + { + foreach (var project in projects) + { + var folder = Path.GetDirectoryName(project); + + _packageSourceManager.Add(FindSolutionFolder(project) ?? folder, "ABP Nightly", + "https://www.myget.org/F/abp-nightly/api/v3/index.json"); + + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( + project, + true); + + await _npmPackagesUpdater.Update( + folder, + true); + } + } + + private async Task SwitchSolutionsToNightlyPreview(List solutionPaths) + { foreach (var solutionPath in solutionPaths) { var solutionFolder = Path.GetDirectoryName(solutionPath); var solutionAngularFolder = GetSolutionAngularFolder(solutionFolder); - _packageSourceManager.Remove(solutionFolder, "ABP Nightly"); + _packageSourceManager.Add(solutionFolder, "ABP Nightly", + "https://www.myget.org/F/abp-nightly/api/v3/index.json"); if (solutionPath != null) { await _nugetPackagesVersionUpdater.UpdateSolutionAsync( solutionPath, - false, - false, true); } await _npmPackagesUpdater.Update( solutionFolder, - false, - false, true); if (solutionAngularFolder != null) { await _npmPackagesUpdater.Update( solutionAngularFolder, - false, - false, true); } } @@ -126,22 +223,18 @@ public class PackagePreviewSwitcher : ITransientDependency private List GetSolutionPaths(CommandLineArgs commandLineArgs) { - var directory = commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) - ?? Directory.GetCurrentDirectory(); - - var solutionPaths = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories); - - if (!solutionPaths.Any()) - { - Logger.LogWarning("No solution (.sln) found to change version."); - } + return Directory.GetFiles(GetDirectory(commandLineArgs), "*.sln", SearchOption.AllDirectories).ToList(); + } - return solutionPaths.ToList(); + private List GetProjectPaths(CommandLineArgs commandLineArgs) + { + return Directory.GetFiles(GetDirectory(commandLineArgs), "*.csproj", SearchOption.AllDirectories).ToList(); } - private string GetSolutionFolder(CommandLineArgs commandLineArgs) + private string GetDirectory(CommandLineArgs commandLineArgs) { return commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) + ?? commandLineArgs.Options.GetOrNull(Options.Directory.Short, Options.Directory.Long) ?? Directory.GetCurrentDirectory(); } @@ -160,6 +253,31 @@ public class PackagePreviewSwitcher : ITransientDependency } return null; + } + + [CanBeNull] + private string FindSolutionFolder(string projectFile) + { + var targetFolder = Path.GetDirectoryName(projectFile); + + do + { + if (Directory.GetParent(targetFolder) != null) + { + targetFolder = Directory.GetParent(targetFolder).FullName; + } + else + { + return Path.GetDirectoryName(projectFile); + } + + if (Directory.GetFiles(targetFolder, "*.sln", SearchOption.TopDirectoryOnly).Any()) + { + break; + } + } while (targetFolder != null); + + return targetFolder; } public static class Options @@ -169,5 +287,10 @@ public class PackagePreviewSwitcher : ITransientDependency public const string Short = "sd"; public const string Long = "solution-directory"; } + public static class Directory + { + public const string Short = "d"; + public const string Long = "directory"; + } } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Studio/AbpStudioAnalyzeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Studio/AbpStudioAnalyzeHelper.cs new file mode 100644 index 0000000000..2504944e13 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Studio/AbpStudioAnalyzeHelper.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Studio; + +public static class AbpStudioAnalyzeHelper +{ + public static bool IsInAnalyzeMode { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs index e24e81ca21..578587f449 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs @@ -1,17 +1,27 @@ using System; +using System.Threading; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Tracing; public class DefaultCorrelationIdProvider : ICorrelationIdProvider, ISingletonDependency { - public string Get() + private readonly AsyncLocal _currentCorrelationId = new AsyncLocal(); + + private string? CorrelationId => _currentCorrelationId.Value; + + public virtual string? Get() { - return CreateNewCorrelationId(); + return CorrelationId; } - protected virtual string CreateNewCorrelationId() + public virtual IDisposable Change(string? correlationId) { - return Guid.NewGuid().ToString("N"); + var parent = CorrelationId; + _currentCorrelationId.Value = correlationId; + return new DisposeAction(() => + { + _currentCorrelationId.Value = parent; + }); } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs index dd94ccb93d..06c3200877 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs @@ -1,9 +1,10 @@ -using JetBrains.Annotations; +using System; namespace Volo.Abp.Tracing; public interface ICorrelationIdProvider { - [NotNull] - string Get(); + string? Get(); + + IDisposable Change(string? correlationId); } diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprSerializer.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprSerializer.cs index 9a8b4c9520..1d1861cd97 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprSerializer.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprSerializer.cs @@ -6,6 +6,8 @@ public interface IDaprSerializer { byte[] Serialize(object obj); + string SerializeToString(object obj); + object Deserialize(byte[] value, Type type); object Deserialize(string value, Type type); diff --git a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/Utf8JsonDaprSerializer.cs b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/Utf8JsonDaprSerializer.cs index c0924f775b..a1a8324598 100644 --- a/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/Utf8JsonDaprSerializer.cs +++ b/framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/Utf8JsonDaprSerializer.cs @@ -19,6 +19,11 @@ public class Utf8JsonDaprSerializer : IDaprSerializer, ITransientDependency return Encoding.UTF8.GetBytes(_jsonSerializer.Serialize(obj)); } + public string SerializeToString(object obj) + { + return _jsonSerializer.Serialize(obj); + } + public object Deserialize(byte[] value, Type type) { return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs index c4f8d9674b..069b7bb85b 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs @@ -24,4 +24,16 @@ public class AbpDatabaseInfo DatabaseName = databaseName; MappedConnections = new HashSet(); } + + /// + /// Shortcut method to add one or multiple connections to the collection. + /// + /// + public void MapConnection(params string[] connectionNames) + { + foreach (var connectionName in connectionNames) + { + MappedConnections.AddIfNotContains(connectionName); + } + } } diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs new file mode 100644 index 0000000000..739571e241 --- /dev/null +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.EventBus; + +namespace Volo.Abp.Data; + +[Serializable] +[EventName("abp.data.applied_database_migrations")] +public class AppliedDatabaseMigrationsEto +{ + public string DatabaseName { get; set; } + public Guid? TenantId { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj index bde6d12081..ae01d66fd0 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj @@ -22,6 +22,7 @@ + diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs index 76015e2f13..7d83d3e603 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs @@ -1,5 +1,6 @@ using Volo.Abp.Application.Localization.Resources.AbpDdd; using Volo.Abp.Auditing; +using Volo.Abp.Data; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -8,7 +9,8 @@ namespace Volo.Abp.Application; [DependsOn( typeof(AbpLocalizationModule), - typeof(AbpAuditingContractsModule) + typeof(AbpAuditingContractsModule), + typeof(AbpDataModule) )] public class AbpDddApplicationContractsModule : AbpModule { diff --git a/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo.Abp.Ddd.Domain.Shared.csproj b/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo.Abp.Ddd.Domain.Shared.csproj index 489d2cf271..2972ef23c1 100644 --- a/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo.Abp.Ddd.Domain.Shared.csproj +++ b/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo.Abp.Ddd.Domain.Shared.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo/Abp/Domain/AbpDddDomainSharedModule.cs b/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo/Abp/Domain/AbpDddDomainSharedModule.cs index 3276f22310..2ddf948022 100644 --- a/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo/Abp/Domain/AbpDddDomainSharedModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain.Shared/Volo/Abp/Domain/AbpDddDomainSharedModule.cs @@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain; [DependsOn( - typeof(AbpMultiTenancyModule), + typeof(AbpMultiTenancyAbstractionsModule), typeof(AbpEventBusAbstractionsModule) )] public class AbpDddDomainSharedModule : AbpModule diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs index c891f3a406..986c19bbbf 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs @@ -48,13 +48,20 @@ public class IncomingEventRecord : public IncomingEventInfo ToIncomingEventInfo() { - return new IncomingEventInfo( + var info = new IncomingEventInfo( Id, MessageId, EventName, EventData, CreationTime ); + + foreach (var property in ExtraProperties) + { + info.SetProperty(property.Key, property.Value); + } + + return info; } public void MarkAsProcessed(DateTime processedTime) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs index 41c4d41ce4..fe639411a2 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs @@ -41,11 +41,18 @@ public class OutgoingEventRecord : public OutgoingEventInfo ToOutgoingEventInfo() { - return new OutgoingEventInfo( + var info = new OutgoingEventInfo( Id, EventName, EventData, CreationTime ); + + foreach (var property in ExtraProperties) + { + info.SetProperty(property.Key, property.Value); + } + + return info; } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreDatabaseMigrationEventHandlerBase.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreDatabaseMigrationEventHandlerBase.cs new file mode 100644 index 0000000000..d9fc7db914 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreDatabaseMigrationEventHandlerBase.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.Migrations; + +public abstract class EfCoreDatabaseMigrationEventHandlerBase : + IDistributedEventHandler, + IDistributedEventHandler, + IDistributedEventHandler, + ITransientDependency + where TDbContext : DbContext, IEfCoreDbContext +{ + protected string DatabaseName { get; } + + protected const string TryCountPropertyName = "__TryCount"; + + protected int MaxEventTryCount { get; set; } = 3; + + /// + /// As milliseconds. + /// + protected int MinValueToWaitOnFailure { get; set; } = 5000; + + /// + /// As milliseconds. + /// + protected int MaxValueToWaitOnFailure { get; set; } = 15000; + + protected ICurrentTenant CurrentTenant { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected ITenantStore TenantStore { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected ILogger> Logger { get; } + + protected EfCoreDatabaseMigrationEventHandlerBase( + string databaseName, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + ITenantStore tenantStore, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory) + { + CurrentTenant = currentTenant; + UnitOfWorkManager = unitOfWorkManager; + TenantStore = tenantStore; + DatabaseName = databaseName; + DistributedEventBus = distributedEventBus; + + Logger = loggerFactory.CreateLogger>(); + } + + public virtual async Task HandleEventAsync(ApplyDatabaseMigrationsEto eventData) + { + if (eventData.DatabaseName != DatabaseName) + { + return; + } + + var schemaMigrated = false; + try + { + schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.TenantId); + await SeedAsync(eventData.TenantId); + + if (schemaMigrated) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = eventData.TenantId + } + ); + } + } + catch (Exception ex) + { + await HandleErrorOnApplyDatabaseMigrationAsync(eventData, ex); + } + + await AfterApplyDatabaseMigrations(eventData, schemaMigrated); + } + + protected virtual Task AfterApplyDatabaseMigrations(ApplyDatabaseMigrationsEto eventData, bool schemaMigrated) + { + return Task.CompletedTask; + } + + public virtual async Task HandleEventAsync(TenantCreatedEto eventData) + { + var schemaMigrated = false; + try + { + schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.Id); + await SeedAsync(eventData.Id); + + if (schemaMigrated) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = eventData.Id + } + ); + } + } + catch (Exception ex) + { + await HandleErrorTenantCreatedAsync(eventData, ex); + } + + await AfterTenantCreated(eventData, schemaMigrated); + } + + protected virtual Task AfterTenantCreated(TenantCreatedEto eventData, bool schemaMigrated) + { + return Task.CompletedTask; + } + + public virtual async Task HandleEventAsync(TenantConnectionStringUpdatedEto eventData) + { + if (eventData.ConnectionStringName != DatabaseName && + eventData.ConnectionStringName != Volo.Abp.Data.ConnectionStrings.DefaultConnectionStringName || + eventData.NewValue.IsNullOrWhiteSpace()) + { + return; + } + + var schemaMigrated = false; + try + { + schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.Id); + await SeedAsync(eventData.Id); + + if (schemaMigrated) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = eventData.Id + } + ); + } + } + catch (Exception ex) + { + await HandleErrorTenantConnectionStringUpdatedAsync(eventData, ex); + } + + await AfterTenantConnectionStringUpdated(eventData, schemaMigrated); + } + + protected virtual Task AfterTenantConnectionStringUpdated(TenantConnectionStringUpdatedEto eventData, + bool schemaMigrated) + { + return Task.CompletedTask; + } + + protected virtual Task SeedAsync(Guid? tenantId) + { + return Task.CompletedTask; + } + + /// + /// Apply pending EF Core schema migrations to the database. + /// Returns true if any migration has applied. + /// + protected virtual async Task MigrateDatabaseSchemaAsync(Guid? tenantId) + { + var result = false; + + using (CurrentTenant.Change(tenantId)) + { + using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + async Task MigrateDatabaseSchemaWithDbContextAsync() + { + var dbContext = await uow.ServiceProvider + .GetRequiredService>() + .GetDbContextAsync(); + + if ((await dbContext.Database.GetPendingMigrationsAsync()).Any()) + { + await dbContext.Database.MigrateAsync(); + return true; + } + + return false; + } + + if (tenantId == null) + { + //Migrating the host database + Logger.LogInformation($"Migrating database of host. Database Name = {DatabaseName}"); + result = await MigrateDatabaseSchemaWithDbContextAsync(); + } + else + { + var tenantConfiguration = await TenantStore.FindAsync(tenantId.Value); + if (!tenantConfiguration.ConnectionStrings.Default.IsNullOrWhiteSpace() || + !tenantConfiguration.ConnectionStrings.GetOrDefault(DatabaseName).IsNullOrWhiteSpace()) + { + //Migrating the tenant database (only if tenant has a separate database) + Logger.LogInformation( + $"Migrating separate database of tenant. Database Name = {DatabaseName}, TenantId = {tenantId}"); + result = await MigrateDatabaseSchemaWithDbContextAsync(); + } + } + + await uow.CompleteAsync(); + } + } + + return result; + } + + protected virtual async Task HandleErrorOnApplyDatabaseMigrationAsync( + ApplyDatabaseMigrationsEto eventData, + Exception exception) + { + var tryCount = IncrementEventTryCount(eventData); + if (tryCount <= MaxEventTryCount) + { + Logger.LogWarning( + $"Could not apply database migrations. Re-queueing the operation. TenantId = {eventData.TenantId}, Database Name = {eventData.DatabaseName}."); + Logger.LogException(exception, LogLevel.Warning); + + await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure)); + await DistributedEventBus.PublishAsync(eventData); + } + else + { + Logger.LogError( + $"Could not apply database migrations. Canceling the operation. TenantId = {eventData.TenantId}, DatabaseName = {eventData.DatabaseName}."); + Logger.LogException(exception); + } + } + + protected virtual async Task HandleErrorTenantCreatedAsync( + TenantCreatedEto eventData, + Exception exception) + { + var tryCount = IncrementEventTryCount(eventData); + if (tryCount <= MaxEventTryCount) + { + Logger.LogWarning( + $"Could not perform tenant created event. Re-queueing the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception, LogLevel.Warning); + + await Task.Delay(RandomHelper.GetRandom(5000, 15000)); + await DistributedEventBus.PublishAsync(eventData); + } + else + { + Logger.LogError( + $"Could not perform tenant created event. Canceling the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception); + } + } + + protected virtual async Task HandleErrorTenantConnectionStringUpdatedAsync( + TenantConnectionStringUpdatedEto eventData, + Exception exception) + { + var tryCount = IncrementEventTryCount(eventData); + if (tryCount <= MaxEventTryCount) + { + Logger.LogWarning( + $"Could not perform tenant connection string updated event. Re-queueing the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception, LogLevel.Warning); + + await Task.Delay(RandomHelper.GetRandom(5000, 15000)); + await DistributedEventBus.PublishAsync(eventData); + } + else + { + Logger.LogError( + $"Could not perform tenant connection string updated event. Canceling the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}."); + Logger.LogException(exception); + } + } + + private static int GetEventTryCount(EtoBase eventData) + { + var tryCountAsString = eventData.Properties.GetOrDefault(TryCountPropertyName); + if (tryCountAsString.IsNullOrEmpty()) + { + return 0; + } + + return int.Parse(tryCountAsString); + } + + private static void SetEventTryCount(EtoBase eventData, int count) + { + eventData.Properties[TryCountPropertyName] = count.ToString(); + } + + private static int IncrementEventTryCount(EtoBase eventData) + { + var count = GetEventTryCount(eventData) + 1; + SetEventTryCount(eventData, count); + return count; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs new file mode 100644 index 0000000000..9b9719ff1b --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs @@ -0,0 +1,145 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DistributedLocking; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.Migrations; + +public abstract class EfCoreRuntimeDatabaseMigratorBase : ITransientDependency + where TDbContext : DbContext, IEfCoreDbContext +{ + protected int MinValueToWaitOnFailure { get; set; } = 5000; + protected int MaxValueToWaitOnFailure { get; set; } = 15000; + + protected string DatabaseName { get; } + + /// + /// Enabling this might be inefficient if you have many tenants! + /// If disabled (default), tenant databases will be seeded only + /// if there is a schema migration applied to the host database. + /// If enabled, tenant databases will be seeded always on every service startup. + /// + protected bool AlwaysSeedTenantDatabases { get; set; } = false; + + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IServiceProvider ServiceProvider { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected ILogger> Logger { get; } + + protected EfCoreRuntimeDatabaseMigratorBase( + string databaseName, + IUnitOfWorkManager unitOfWorkManager, + IServiceProvider serviceProvider, + ICurrentTenant currentTenant, + IAbpDistributedLock abpDistributedLock, + IDistributedEventBus distributedEventBus, + ILoggerFactory loggerFactory) + { + DatabaseName = databaseName; + UnitOfWorkManager = unitOfWorkManager; + ServiceProvider = serviceProvider; + CurrentTenant = currentTenant; + DistributedLock = abpDistributedLock; + DistributedEventBus = distributedEventBus; + Logger = loggerFactory.CreateLogger>(); + } + + public virtual async Task CheckAndApplyDatabaseMigrationsAsync() + { + await TryAsync(LockAndApplyDatabaseMigrationsAsync); + } + + protected virtual async Task LockAndApplyDatabaseMigrationsAsync() + { + Logger.LogInformation($"Trying to acquire the distributed lock for database migration: {DatabaseName}."); + + var schemaMigrated = false; + + await using (var handle = await DistributedLock.TryAcquireAsync("DatabaseMigration_" + DatabaseName)) + { + if (handle is null) + { + Logger.LogInformation($"Distributed lock could not be acquired for database migration: {DatabaseName}. Operation cancelled."); + return; + } + + Logger.LogInformation($"Distributed lock is acquired for database migration: {DatabaseName}..."); + + using (CurrentTenant.Change(null)) + { + // Create database tables if needed + using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + var dbContext = await ServiceProvider + .GetRequiredService>() + .GetDbContextAsync(); + + var pendingMigrations = await dbContext + .Database + .GetPendingMigrationsAsync(); + + if (pendingMigrations.Any()) + { + await dbContext.Database.MigrateAsync(); + schemaMigrated = true; + } + + await uow.CompleteAsync(); + } + } + + await SeedAsync(); + + if (schemaMigrated || AlwaysSeedTenantDatabases) + { + await DistributedEventBus.PublishAsync( + new AppliedDatabaseMigrationsEto + { + DatabaseName = DatabaseName, + TenantId = null + } + ); + } + } + + Logger.LogInformation($"Distributed lock has been released for database migration: {DatabaseName}..."); + } + + protected virtual Task SeedAsync() + { + return Task.CompletedTask; + } + + protected virtual async Task TryAsync(Func task, int maxTryCount = 3) + { + try + { + await task(); + } + catch (Exception ex) + { + maxTryCount--; + + if (maxTryCount <= 0) + { + throw; + } + + Logger.LogWarning($"{ex.GetType().Name} has been thrown. The operation will be tried {maxTryCount} times more. Exception:\n{ex.Message}. Stack Trace:\n{ex.StackTrace}"); + + await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure)); + + await TryAsync(task, maxTryCount); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index 5c290633de..949d1ebc1a 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -18,7 +18,7 @@ namespace Volo.Abp.Uow.EntityFrameworkCore; public class UnitOfWorkDbContextProvider : IDbContextProvider where TDbContext : IEfCoreDbContext { - private const string TransactionsNotSupportedErrorMessage = "Current database does not support transactions. Your database may remain in an inconsistent state in an error case."; + private const string TransactionsNotSupportedWarningMessage = "Current database does not support transactions. Your database may remain in an inconsistent state in an error case."; public ILogger> Logger { get; set; } @@ -189,8 +189,7 @@ public class UnitOfWorkDbContextProvider : IDbContextProvider : IDbContextProvider : IDbContextProvider : IDbContextProvider : IDbContextProvider : IDbContextProvider outgoingEvents, OutboxConfig outboxConfig) @@ -125,18 +135,23 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen message.MessageId = outgoingEvent.Id.ToString(); } + message.CorrelationId = outgoingEvent.GetCorrelationId(); + if (!messageBatch.TryAddMessage(message)) { throw new AbpException( "The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number"); } - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } } await publisher.SendMessagesAsync(messageBatch); @@ -152,7 +167,10 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); var exceptions = new List(); - await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId())) + { + await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + } if (exceptions.Any()) { ThrowOriginalExceptions(eventType, exceptions); @@ -244,12 +262,13 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen { var body = Serializer.Serialize(eventData); - return PublishAsync(eventName, body, null); + return PublishAsync(eventName, body, CorrelationIdProvider.Get(), null); } protected virtual async Task PublishAsync( string eventName, byte[] body, + [CanBeNull] string correlationId, Guid? eventId) { var message = new ServiceBusMessage(body) @@ -262,6 +281,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen message.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N"); } + message.CorrelationId = correlationId; + var publisher = await PublisherPool.GetAsync( Options.TopicName, Options.ConnectionName); diff --git a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/AbpDaprEventData.cs b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/AbpDaprEventData.cs new file mode 100644 index 0000000000..ee08586b8d --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/AbpDaprEventData.cs @@ -0,0 +1,23 @@ +namespace Volo.Abp.EventBus.Dapr; + +public class AbpDaprEventData +{ + public string PubSubName { get; set; } + + public string Topic { get; set; } + + public string MessageId { get; set; } + + public string JsonData { get; set; } + + public string CorrelationId { get; set; } + + public AbpDaprEventData(string pubSubName, string topic, string messageId, string jsonData, string correlationId) + { + PubSubName = pubSubName; + Topic = topic; + MessageId = messageId; + JsonData = jsonData; + CorrelationId = correlationId; + } +} diff --git a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs index 23cf8aa1e9..4912a58388 100644 --- a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs @@ -13,6 +13,7 @@ using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Timing; +using Volo.Abp.Tracing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.Dapr; @@ -39,8 +40,17 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend IDaprSerializer serializer, IOptions daprEventBusOptions, IAbpDaprClientFactory daprClientFactory, - ILocalEventBus localEventBus) - : base(serviceScopeFactory, currentTenant, unitOfWorkManager, abpDistributedEventBusOptions, guidGenerator, clock, eventHandlerInvoker, localEventBus) + ILocalEventBus localEventBus, + ICorrelationIdProvider correlationIdProvider) + : base(serviceScopeFactory, + currentTenant, + unitOfWorkManager, + abpDistributedEventBusOptions, + guidGenerator, + clock, + eventHandlerInvoker, + localEventBus, + correlationIdProvider) { Serializer = serializer; DaprEventBusOptions = daprEventBusOptions.Value; @@ -119,9 +129,9 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - await PublishToDaprAsync(eventType, eventData); + await PublishToDaprAsync(eventType, eventData, null, CorrelationIdProvider.Get()); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -141,43 +151,52 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend return handlerFactoryList.ToArray(); } - public override async Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) + public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } - await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName))); + await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId()); } - public override async Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { var outgoingEventArray = outgoingEvents.ToArray(); foreach (var outgoingEvent in outgoingEventArray) { - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } - await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName))); + await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId()); } } - public virtual async Task TriggerHandlersAsync(string messageId, Type eventType, object eventData) + public virtual async Task TriggerHandlersAsync(Type eventType, object eventData, string messageId = null, string correlationId = null) { - if (await AddToInboxAsync(messageId, EventNameAttribute.GetNameOrDefault(eventType), eventType, eventData)) + if (await AddToInboxAsync(messageId, EventNameAttribute.GetNameOrDefault(eventType), eventType, eventData, correlationId)) { return; } - await TriggerHandlersDirectAsync(eventType, eventData); + using (CorrelationIdProvider.Change(correlationId)) + { + await TriggerHandlersDirectAsync(eventType, eventData); + } } public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) @@ -190,7 +209,10 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); var exceptions = new List(); - await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId())) + { + await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + } if (exceptions.Any()) { ThrowOriginalExceptions(eventType, exceptions); @@ -226,15 +248,16 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend return EventTypes.GetOrDefault(eventName); } - protected virtual async Task PublishToDaprAsync(Type eventType, object eventData) + protected virtual async Task PublishToDaprAsync(Type eventType, object eventData, Guid? messageId = null, string correlationId = null) { - await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData); + await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData, messageId, correlationId); } - protected virtual async Task PublishToDaprAsync(string eventName, object eventData) + protected virtual async Task PublishToDaprAsync(string eventName, object eventData, Guid? messageId = null, string correlationId = null) { var client = DaprClientFactory.Create(); - await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: eventData); + var data = new AbpDaprEventData(DaprEventBusOptions.PubSubName, eventName, (messageId ?? GuidGenerator.Create()).ToString("N"), Serializer.SerializeToString(eventData), correlationId); + await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: data); } private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 62e161fad1..b49ffc07a9 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -14,6 +14,7 @@ using Volo.Abp.Kafka; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Timing; +using Volo.Abp.Tracing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.Kafka; @@ -42,7 +43,8 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen IGuidGenerator guidGenerator, IClock clock, IEventHandlerInvoker eventHandlerInvoker, - ILocalEventBus localEventBus) + ILocalEventBus localEventBus, + ICorrelationIdProvider correlationIdProvider) : base( serviceScopeFactory, currentTenant, @@ -51,7 +53,8 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen guidGenerator, clock, eventHandlerInvoker, - localEventBus) + localEventBus, + correlationIdProvider) { AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; MessageConsumerFactory = messageConsumerFactory; @@ -84,13 +87,17 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen var messageId = message.GetMessageId(); var eventData = Serializer.Deserialize(message.Value, eventType); + var correlationId = message.GetCorrelationId(); - if (await AddToInboxAsync(messageId, eventName, eventType, eventData)) + if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId)) { return; } - await TriggerHandlersDirectAsync(eventType, eventData); + using (CorrelationIdProvider.Change(correlationId)) + { + await TriggerHandlersDirectAsync(eventType, eventData); + } } public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) @@ -163,14 +170,21 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { + var headers = new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } + }; + + if (CorrelationIdProvider.Get() != null) + { + headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(CorrelationIdProvider.Get()!)); + } + await PublishAsync( AbpKafkaEventBusOptions.TopicName, eventType, eventData, - new Headers - { - { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } - } + headers ); } @@ -179,25 +193,34 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen unitOfWork.AddOrReplaceDistributedEvent(eventRecord); } - public override async Task PublishFromOutboxAsync( + public async override Task PublishFromOutboxAsync( OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } + + var headers = new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } + }; + if (outgoingEvent.GetCorrelationId() != null) + { + headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!)); + } await PublishAsync( AbpKafkaEventBusOptions.TopicName, outgoingEvent.EventName, outgoingEvent.EventData, - new Headers - { - { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } - } + headers ); } @@ -214,12 +237,20 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} }; - await TriggerDistributedEventSentAsync(new DistributedEventSent() + if (outgoingEvent.GetCorrelationId() != null) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!)); + } + + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) + { + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } producer.Produce( AbpKafkaEventBusOptions.TopicName, @@ -244,7 +275,10 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); var exceptions = new List(); - await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId())) + { + await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + } if (exceptions.Any()) { ThrowOriginalExceptions(eventType, exceptions); diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs index 17a80ec87c..569e56a1dc 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs @@ -15,4 +15,16 @@ public static class MessageExtensions return messageId; } + + public static string GetCorrelationId(this Message message) + { + string correlationId = null; + + if (message.Headers.TryGetLastBytes(EventBusConsts.CorrelationIdHeaderName, out var correlationIdBytes)) + { + correlationId = System.Text.Encoding.UTF8.GetString(correlationIdBytes); + } + + return correlationId; + } } diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 8d4756165a..a2459ea8d4 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using RabbitMQ.Client; @@ -15,6 +16,7 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.RabbitMQ; using Volo.Abp.Threading; using Volo.Abp.Timing; +using Volo.Abp.Tracing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.RabbitMq; @@ -49,7 +51,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe IGuidGenerator guidGenerator, IClock clock, IEventHandlerInvoker eventHandlerInvoker, - ILocalEventBus localEventBus) + ILocalEventBus localEventBus, + ICorrelationIdProvider correlationIdProvider) : base( serviceScopeFactory, currentTenant, @@ -58,7 +61,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe guidGenerator, clock, eventHandlerInvoker, - localEventBus) + localEventBus, + correlationIdProvider) { ConnectionPool = connectionPool; Serializer = serializer; @@ -103,12 +107,16 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); - if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventData)) + var correlationId = ea.BasicProperties.CorrelationId; + if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventData, correlationId)) { return; } - await TriggerHandlersDirectAsync(eventType, eventData); + using (CorrelationIdProvider.Change(correlationId)) + { + await TriggerHandlersDirectAsync(eventType, eventData); + } } public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) @@ -186,7 +194,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - await PublishAsync(eventType, eventData, null); + await PublishAsync(eventType, eventData, correlationId: CorrelationIdProvider.Get()); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -194,18 +202,21 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe unitOfWork.AddOrReplaceDistributedEvent(eventRecord); } - public override async Task PublishFromOutboxAsync( + public async override Task PublishFromOutboxAsync( OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } - await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); + await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, eventId: outgoingEvent.Id, correlationId: outgoingEvent.GetCorrelationId()); } public async override Task PublishManyFromOutboxAsync( @@ -219,19 +230,22 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe foreach (var outgoingEvent in outgoingEventArray) { - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } await PublishAsync( channel, outgoingEvent.EventName, outgoingEvent.EventData, - properties: null, - eventId: outgoingEvent.Id); + eventId: outgoingEvent.Id, + correlationId: outgoingEvent.GetCorrelationId()); } channel.WaitForConfirmsOrDie(); @@ -250,7 +264,10 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); var exceptions = new List(); - await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId())) + { + await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + } if (exceptions.Any()) { ThrowOriginalExceptions(eventType, exceptions); @@ -262,28 +279,29 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe return Serializer.Serialize(eventData); } - public Task PublishAsync( + public virtual Task PublishAsync( Type eventType, object eventData, - IBasicProperties properties, - Dictionary headersArguments = null) + Dictionary headersArguments = null, + Guid? eventId = null, + [CanBeNull] string correlationId = null) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); - return PublishAsync(eventName, body, properties, headersArguments); + return PublishAsync( eventName, body, headersArguments, eventId, correlationId); } protected virtual Task PublishAsync( string eventName, byte[] body, - IBasicProperties properties, Dictionary headersArguments = null, - Guid? eventId = null) + Guid? eventId = null, + [CanBeNull] string correlationId = null) { using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) { - return PublishAsync(channel, eventName, body, properties, headersArguments, eventId); + return PublishAsync(channel, eventName, body, headersArguments, eventId, correlationId); } } @@ -291,23 +309,25 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe IModel channel, string eventName, byte[] body, - IBasicProperties properties, Dictionary headersArguments = null, - Guid? eventId = null) + Guid? eventId = null, + [CanBeNull] string correlationId = null) { EnsureExchangeExists(channel); - if (properties == null) - { - properties = channel.CreateBasicProperties(); - properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; - } + var properties = channel.CreateBasicProperties(); + properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; if (properties.MessageId.IsNullOrEmpty()) { properties.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N"); } + if (correlationId != null) + { + properties.CorrelationId = correlationId; + } + SetEventMessageHeaders(properties, headersArguments); channel.BasicPublish( diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 0983e98836..560b13ba14 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -16,6 +16,7 @@ using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Timing; +using Volo.Abp.Tracing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.Rebus; @@ -43,7 +44,8 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen IGuidGenerator guidGenerator, IClock clock, IEventHandlerInvoker eventHandlerInvoker, - ILocalEventBus localEventBus) : + ILocalEventBus localEventBus, + ICorrelationIdProvider correlationIdProvider) : base( serviceScopeFactory, currentTenant, @@ -52,7 +54,8 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen guidGenerator, clock, eventHandlerInvoker, - localEventBus) + localEventBus, + correlationIdProvider) { Rebus = rebus; Serializer = serializer; @@ -144,18 +147,27 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { var messageId = MessageContext.Current.TransportMessage.GetMessageId(); var eventName = EventNameAttribute.GetNameOrDefault(eventType); + var correlationId = MessageContext.Current.Headers.GetOrDefault(EventBusConsts.CorrelationIdHeaderName); - if (await AddToInboxAsync(messageId, eventName, eventType, eventData)) + if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId)) { return; } - await TriggerHandlersDirectAsync(eventType, eventData); + using (CorrelationIdProvider.Change(correlationId)) + { + await TriggerHandlersDirectAsync(eventType, eventData); + } } protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - await PublishAsync(eventType, eventData); + var headers = new Dictionary(); + if (CorrelationIdProvider.Get() != null) + { + headers.Add(EventBusConsts.CorrelationIdHeaderName, CorrelationIdProvider.Get()); + } + await PublishAsync(eventType, eventData, headersArguments: headers); } protected virtual async Task PublishAsync( @@ -234,21 +246,29 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return false; } - public override async Task PublishFromOutboxAsync( + public async override Task PublishFromOutboxAsync( OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName); var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType); - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } - await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id); + var headers = new Dictionary(); + if (outgoingEvent.GetCorrelationId() != null) + { + headers.Add(EventBusConsts.CorrelationIdHeaderName, outgoingEvent.GetCorrelationId()); + } + + await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id, headersArguments: headers); } public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) @@ -259,12 +279,15 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { foreach (var outgoingEvent in outgoingEventArray) { - await TriggerDistributedEventSentAsync(new DistributedEventSent() + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { - Source = DistributedEventSource.Outbox, - EventName = outgoingEvent.EventName, - EventData = outgoingEvent.EventData - }); + await TriggerDistributedEventSentAsync(new DistributedEventSent() + { + Source = DistributedEventSource.Outbox, + EventName = outgoingEvent.EventName, + EventData = outgoingEvent.EventData + }); + } await PublishFromOutboxAsync(outgoingEvent, outboxConfig); } @@ -285,7 +308,10 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); var exceptions = new List(); - await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId())) + { + await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig); + } if (exceptions.Any()) { ThrowOriginalExceptions(eventType, exceptions); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index 89423ce111..2ca5c85bb0 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.EventBus.Local; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.Timing; +using Volo.Abp.Tracing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.Distributed; @@ -18,6 +20,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB protected IClock Clock { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected ILocalEventBus LocalEventBus { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } protected DistributedEventBusBase( IServiceScopeFactory serviceScopeFactory, @@ -27,7 +30,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB IGuidGenerator guidGenerator, IClock clock, IEventHandlerInvoker eventHandlerInvoker, - ILocalEventBus localEventBus) : base( + ILocalEventBus localEventBus, + ICorrelationIdProvider correlationIdProvider) : base( serviceScopeFactory, currentTenant, unitOfWorkManager, @@ -37,6 +41,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB Clock = clock; AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; LocalEventBus = localEventBus; + CorrelationIdProvider = correlationIdProvider; } public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class @@ -129,14 +134,14 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB EventData = eventData }); - await eventOutbox.EnqueueAsync( - new OutgoingEventInfo( - GuidGenerator.Create(), - eventName, - Serialize(eventData), - Clock.Now - ) + var outgoingEventInfo = new OutgoingEventInfo( + GuidGenerator.Create(), + eventName, + Serialize(eventData), + Clock.Now ); + outgoingEventInfo.SetCorrelationId(CorrelationIdProvider.Get()); + await eventOutbox.EnqueueAsync(outgoingEventInfo); return true; } } @@ -153,7 +158,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB string messageId, string eventName, Type eventType, - object eventData) + object eventData, + [CanBeNull] string correlationId) { if (AbpDistributedEventBusOptions.Inboxes.Count <= 0) { @@ -177,22 +183,25 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB } } - await TriggerDistributedEventReceivedAsync(new DistributedEventReceived + using (CorrelationIdProvider.Change(correlationId)) { - Source = DistributedEventSource.Direct, - EventName = EventNameAttribute.GetNameOrDefault(eventType), - EventData = eventData - }); - - await eventInbox.EnqueueAsync( - new IncomingEventInfo( - GuidGenerator.Create(), - messageId, - eventName, - Serialize(eventData), - Clock.Now - ) + await TriggerDistributedEventReceivedAsync(new DistributedEventReceived + { + Source = DistributedEventSource.Direct, + EventName = EventNameAttribute.GetNameOrDefault(eventType), + EventData = eventData + }); + } + + var incomingEventInfo = new IncomingEventInfo( + GuidGenerator.Create(), + messageId, + eventName, + Serialize(eventData), + Clock.Now ); + incomingEventInfo.SetCorrelationId(correlationId); + await eventInbox.EnqueueAsync(incomingEventInfo); } } } diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj b/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj index 142b143632..de9dc60c0d 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo.Abp.ExceptionHandling.csproj @@ -13,6 +13,7 @@ + diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/AbpExceptionHandlingModule.cs b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/AbpExceptionHandlingModule.cs index a541c2624d..762de89c82 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/AbpExceptionHandlingModule.cs +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/AbpExceptionHandlingModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.ExceptionHandling.Localization; +using Volo.Abp.Data; +using Volo.Abp.ExceptionHandling.Localization; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -11,7 +12,8 @@ namespace Volo.Abp.ExceptionHandling; * We will re-design this package in a later time, probably with v5.0. */ [DependsOn( - typeof(AbpLocalizationModule) + typeof(AbpLocalizationModule), + typeof(AbpDataModule) )] public class AbpExceptionHandlingModule : AbpModule { diff --git a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj index 40d03c2f15..dfd64e0b1a 100644 --- a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj +++ b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index 32952a7050..6f5f8eda95 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -245,8 +245,9 @@ public class IdentityModelAuthenticationService : IIdentityModelAuthenticationSe throw new AbpException(response.ErrorDescription); } - Logger.LogInformation($"First copy your one-time code: {response.UserCode}"); - Logger.LogInformation($"Open {response.VerificationUri} in your browser..."); + Logger.LogInformation($"Open your browser, go to: \"{response.VerificationUri}\""); + Logger.LogInformation($"and enter the following one-time code:"); + Logger.LogInformation(response.UserCode); for (var i = 0; i < ((response.ExpiresIn ?? 300) / response.Interval + 1); i++) { diff --git a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs index c68c7e3773..11789776c7 100644 --- a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs +++ b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageCompressor.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -16,7 +17,7 @@ public class ImageCompressor : IImageCompressor, ITransientDependency public ImageCompressor(IEnumerable imageCompressorContributors, ICancellationTokenProvider cancellationTokenProvider) { - ImageCompressorContributors = imageCompressorContributors; + ImageCompressorContributors = imageCompressorContributors.Reverse(); CancellationTokenProvider = cancellationTokenProvider; } diff --git a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs index 5576197797..139fc194dd 100644 --- a/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs +++ b/framework/src/Volo.Abp.Imaging.Abstractions/Volo/Abp/Imaging/ImageResizer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -22,7 +23,7 @@ public class ImageResizer : IImageResizer, ITransientDependency IOptions imageResizeOptions, ICancellationTokenProvider cancellationTokenProvider) { - ImageResizerContributors = imageResizerContributors; + ImageResizerContributors = imageResizerContributors.Reverse(); CancellationTokenProvider = cancellationTokenProvider; ImageResizeOptions = imageResizeOptions.Value; } diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo.Abp.Json.SystemTextJson.csproj b/framework/src/Volo.Abp.Json.SystemTextJson/Volo.Abp.Json.SystemTextJson.csproj index 08ea7d91e6..1fc81dd3ac 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo.Abp.Json.SystemTextJson.csproj +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo.Abp.Json.SystemTextJson.csproj @@ -19,6 +19,7 @@ + diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs index 3f193398e3..3afa1b8bde 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs @@ -2,6 +2,7 @@ using System.Text.Encodings.Web; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.Data; using Volo.Abp.Json.SystemTextJson.JsonConverters; using Volo.Abp.Json.SystemTextJson.Modifiers; using Volo.Abp.Modularity; @@ -9,7 +10,7 @@ using Volo.Abp.Timing; namespace Volo.Abp.Json.SystemTextJson; -[DependsOn(typeof(AbpJsonAbstractionsModule), typeof(AbpTimingModule))] +[DependsOn(typeof(AbpJsonAbstractionsModule), typeof(AbpTimingModule), typeof(AbpDataModule))] public class AbpJsonSystemTextJsonModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs index 22c1d860d1..e6ddc87bd6 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs @@ -48,13 +48,20 @@ public class IncomingEventRecord : public IncomingEventInfo ToIncomingEventInfo() { - return new IncomingEventInfo( + var info = new IncomingEventInfo( Id, MessageId, EventName, EventData, CreationTime ); + + foreach (var property in ExtraProperties) + { + info.SetProperty(property.Key, property.Value); + } + + return info; } public void MarkAsProcessed(DateTime processedTime) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs index 15dfac38f6..0f0798532b 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs @@ -41,11 +41,18 @@ public class OutgoingEventRecord : public OutgoingEventInfo ToOutgoingEventInfo() { - return new OutgoingEventInfo( + var info = new OutgoingEventInfo( Id, EventName, EventData, CreationTime ); + + foreach (var property in ExtraProperties) + { + info.SetProperty(property.Key, property.Value); + } + + return info; } } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs index d96d23073b..04d4e0bfa5 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.Uow.MongoDB; public class UnitOfWorkMongoDbContextProvider : IMongoDbContextProvider where TMongoDbContext : IAbpMongoDbContext { - private const string TransactionsNotSupportedErrorMessage = "Current database does not support transactions. Your database may remain in an inconsistent state in an error case."; + private const string TransactionsNotSupportedWarningMessage = "Current database does not support transactions. Your database may remain in an inconsistent state in an error case."; public ILogger> Logger { get; set; } protected readonly IUnitOfWorkManager UnitOfWorkManager; @@ -194,8 +194,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext } catch (NotSupportedException e) { - Logger.LogError(TransactionsNotSupportedErrorMessage); - Logger.LogException(e); + Logger.LogWarning(TransactionsNotSupportedWarningMessage); dbContext.ToAbpMongoDbContext().InitializeDatabase(database, client, null); return dbContext; @@ -245,8 +244,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext } catch (NotSupportedException e) { - Logger.LogError(TransactionsNotSupportedErrorMessage); - Logger.LogException(e); + Logger.LogWarning(TransactionsNotSupportedWarningMessage); dbContext.ToAbpMongoDbContext().InitializeDatabase(database, client, null); return dbContext; diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/FodyWeavers.xml b/framework/src/Volo.Abp.MultiTenancy.Abstractions/FodyWeavers.xml new file mode 100644 index 0000000000..1715698ccd --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/FodyWeavers.xsd b/framework/src/Volo.Abp.MultiTenancy.Abstractions/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.MultiTenancy/System/Security/Principal/AbpMultiTenancyClaimsIdentityExtensions.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/System/Security/Principal/AbpMultiTenancyClaimsIdentityExtensions.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/System/Security/Principal/AbpMultiTenancyClaimsIdentityExtensions.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/System/Security/Principal/AbpMultiTenancyClaimsIdentityExtensions.cs diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj new file mode 100644 index 0000000000..77b0ba2894 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo.Abp.MultiTenancy.Abstractions.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.0;netstandard2.1;net7.0 + Volo.Abp.MultiTenancy.Abstractions + Volo.Abp.MultiTenancy.Abstractions + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs new file mode 100644 index 0000000000..3734adcb71 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyAbstractionsModule.cs @@ -0,0 +1,28 @@ +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.MultiTenancy; + +[DependsOn( + typeof(AbpVirtualFileSystemModule), + typeof(AbpLocalizationModule) +)] +public class AbpMultiTenancyAbstractionsModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Volo/Abp/MultiTenancy/Localization"); + }); + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyOptions.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyOptions.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyOptions.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpMultiTenancyOptions.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs similarity index 64% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs index ff96958c85..414983c87a 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/AbpTenantResolveOptions.cs @@ -10,9 +10,6 @@ public class AbpTenantResolveOptions public AbpTenantResolveOptions() { - TenantResolvers = new List - { - new CurrentUserTenantResolveContributor() - }; + TenantResolvers = new List(); } } diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/BasicTenantInfo.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/BasicTenantInfo.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/BasicTenantInfo.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/BasicTenantInfo.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/AbpDefaultTenantStoreOptions.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/AbpDefaultTenantStoreOptions.cs similarity index 55% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/AbpDefaultTenantStoreOptions.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/AbpDefaultTenantStoreOptions.cs index 3970265b70..478d692cf8 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ConfigurationStore/AbpDefaultTenantStoreOptions.cs +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ConfigurationStore/AbpDefaultTenantStoreOptions.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.MultiTenancy.ConfigurationStore; +using System; + +namespace Volo.Abp.MultiTenancy.ConfigurationStore; public class AbpDefaultTenantStoreOptions { @@ -6,6 +8,6 @@ public class AbpDefaultTenantStoreOptions public AbpDefaultTenantStoreOptions() { - Tenants = new TenantConfiguration[0]; + Tenants = Array.Empty(); } } diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/CurrentTenantExtensions.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentTenantExtensions.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/CurrentTenantExtensions.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/CurrentTenantExtensions.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ICurrentTenant.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ICurrentTenant.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ICurrentTenantAccessor.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenantAccessor.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ICurrentTenantAccessor.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenantAccessor.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/IMultiTenant.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/IMultiTenant.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/IMultiTenant.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/IMultiTenant.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantConfigurationProvider.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantConfigurationProvider.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantConfigurationProvider.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantConfigurationProvider.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveContext.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolveContext.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveContext.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolveContext.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveContributor.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolveContributor.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveContributor.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolveContributor.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveResultAccessor.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolveResultAccessor.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolveResultAccessor.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolveResultAccessor.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolver.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolver.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantResolver.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantResolver.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantStore.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantStore.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/ITenantStore.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ITenantStore.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/IgnoreMultiTenancyAttribute.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/IgnoreMultiTenancyAttribute.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/IgnoreMultiTenancyAttribute.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/IgnoreMultiTenancyAttribute.cs diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/AbpMultiTenancyResource.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/AbpMultiTenancyResource.cs new file mode 100644 index 0000000000..c08fe856d6 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/AbpMultiTenancyResource.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.MultiTenancy.Localization; + +[LocalizationResourceName("AbpMultiTenancy")] +public class AbpMultiTenancyResource +{ + +} diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/en-GB.json b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/en-GB.json new file mode 100644 index 0000000000..0c2b24e68a --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/en-GB.json @@ -0,0 +1,9 @@ +{ + "culture": "en-GB", + "texts": { + "TenantNotFoundMessage": "Tenant not found!", + "TenantNotFoundDetails": "There is no tenant with the tenant id or name: {0}", + "TenantNotActiveMessage": "Tenant is not active!", + "TenantNotActiveDetails": "The tenant is not active with the tenant id or name: {0}" + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/en.json b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/en.json new file mode 100644 index 0000000000..b1242d8437 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/en.json @@ -0,0 +1,9 @@ +{ + "culture": "en", + "texts": { + "TenantNotFoundMessage": "Tenant not found!", + "TenantNotFoundDetails": "There is no tenant with the tenant id or name: {0}", + "TenantNotActiveMessage": "Tenant is not active!", + "TenantNotActiveDetails": "The tenant is not active with the tenant id or name: {0}" + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/tr.json b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/tr.json new file mode 100644 index 0000000000..94a930344c --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/tr.json @@ -0,0 +1,9 @@ +{ + "culture": "tr", + "texts": { + "TenantNotFoundMessage": "Kiracı bulunamadı!", + "TenantNotFoundDetails": "Kiracı kimliğine veya adına sahip bir kiracı yok: {0}", + "TenantNotActiveMessage": "Kiracı aktif değil!", + "TenantNotActiveDetails": "Kiracı, kiracı kimliği veya adıyla etkin değil: {0}" + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/zh-Hans.json b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/zh-Hans.json new file mode 100644 index 0000000000..b093513c77 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "culture": "zh-Hans", + "texts": { + "TenantNotFoundMessage": "找不到租户!", + "TenantNotFoundDetails": "无法找到ID或名称为{0}的租户", + "TenantNotActiveMessage": "租户未启用!", + "TenantNotActiveDetails": "ID或名称为{0}的租户未启用" + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/zh-Hant.json b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/zh-Hant.json new file mode 100644 index 0000000000..218709e7c3 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/Localization/zh-Hant.json @@ -0,0 +1,9 @@ +{ + "culture": "zh-Hant", + "texts": { + "TenantNotFoundMessage": "找不到租戶!", + "TenantNotFoundDetails": "無法找到ID或名稱為{0}的租戶", + "TenantNotActiveMessage": "租戶未啟用!", + "TenantNotActiveDetails": "ID或名稱為{0}的租戶未啟用" + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyDatabaseStyle.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenancyDatabaseStyle.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancyDatabaseStyle.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenancyDatabaseStyle.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancySides.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenancySides.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenancySides.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/MultiTenancySides.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConfiguration.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantConfiguration.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConfiguration.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantConfiguration.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConnectionStringUpdatedEto.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantConnectionStringUpdatedEto.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConnectionStringUpdatedEto.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantConnectionStringUpdatedEto.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantCreatedEto.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantCreatedEto.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantCreatedEto.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantCreatedEto.cs diff --git a/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveContext.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveContext.cs new file mode 100644 index 0000000000..86b0586c56 --- /dev/null +++ b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveContext.cs @@ -0,0 +1,22 @@ +using System; + +namespace Volo.Abp.MultiTenancy; + +public class TenantResolveContext : ITenantResolveContext +{ + public IServiceProvider ServiceProvider { get; } + + public string TenantIdOrName { get; set; } + + public bool Handled { get; set; } + + public bool HasResolvedTenantOrHost() + { + return Handled || TenantIdOrName != null; + } + + public TenantResolveContext(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } +} diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolveResult.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveResult.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolveResult.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolveResult.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolverConsts.cs b/framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverConsts.cs similarity index 100% rename from framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolverConsts.cs rename to framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/TenantResolverConsts.cs diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo.Abp.MultiTenancy.csproj b/framework/src/Volo.Abp.MultiTenancy/Volo.Abp.MultiTenancy.csproj index b8948be49f..e2a247ffd4 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo.Abp.MultiTenancy.csproj +++ b/framework/src/Volo.Abp.MultiTenancy/Volo.Abp.MultiTenancy.csproj @@ -17,7 +17,9 @@ + + diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs index 822af3ff3c..5e2cb4d9ce 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/AbpMultiTenancyModule.cs @@ -1,18 +1,20 @@ -using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; using Volo.Abp.EventBus.Abstractions; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy.ConfigurationStore; using Volo.Abp.Security; +using Volo.Abp.Settings; namespace Volo.Abp.MultiTenancy; -//TODO: Create a Volo.Abp.MultiTenancy.Abstractions package? - [DependsOn( typeof(AbpDataModule), typeof(AbpSecurityModule), - typeof(AbpEventBusAbstractionsModule) + typeof(AbpSettingsModule), + typeof(AbpEventBusAbstractionsModule), + typeof(AbpMultiTenancyAbstractionsModule) )] public class AbpMultiTenancyModule : AbpModule { @@ -22,5 +24,15 @@ public class AbpMultiTenancyModule : AbpModule var configuration = context.Services.GetConfiguration(); Configure(configuration); + + Configure(options => + { + options.ValueProviders.InsertAfter(t => t == typeof(GlobalSettingValueProvider), typeof(TenantSettingValueProvider)); + }); + + Configure(options => + { + options.TenantResolvers.Insert(0, new CurrentUserTenantResolveContributor()); + }); } } diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConfigurationProvider.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConfigurationProvider.cs index 9d104cf7b6..7df558f9f3 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConfigurationProvider.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantConfigurationProvider.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Localization; using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy.Localization; namespace Volo.Abp.MultiTenancy; @@ -9,15 +11,18 @@ public class TenantConfigurationProvider : ITenantConfigurationProvider, ITransi protected virtual ITenantResolver TenantResolver { get; } protected virtual ITenantStore TenantStore { get; } protected virtual ITenantResolveResultAccessor TenantResolveResultAccessor { get; } + protected virtual IStringLocalizer StringLocalizer { get; } public TenantConfigurationProvider( ITenantResolver tenantResolver, ITenantStore tenantStore, - ITenantResolveResultAccessor tenantResolveResultAccessor) + ITenantResolveResultAccessor tenantResolveResultAccessor, + IStringLocalizer stringLocalizer) { TenantResolver = tenantResolver; TenantStore = tenantStore; TenantResolveResultAccessor = tenantResolveResultAccessor; + StringLocalizer = stringLocalizer; } public virtual async Task GetAsync(bool saveResolveResult = false) @@ -38,8 +43,8 @@ public class TenantConfigurationProvider : ITenantConfigurationProvider, ITransi { throw new BusinessException( code: "Volo.AbpIo.MultiTenancy:010001", - message: "Tenant not found!", - details: "There is no tenant with the tenant id or name: " + resolveResult.TenantIdOrName + message: StringLocalizer["TenantNotFoundMessage"], + details: StringLocalizer["TenantNotFoundDetails", resolveResult.TenantIdOrName] ); } @@ -47,8 +52,8 @@ public class TenantConfigurationProvider : ITenantConfigurationProvider, ITransi { throw new BusinessException( code: "Volo.AbpIo.MultiTenancy:010002", - message: "Tenant not active!", - details: "The tenant is no active with the tenant id or name: " + resolveResult.TenantIdOrName + message: StringLocalizer["TenantNotActiveMessage"], + details: StringLocalizer["TenantNotActiveDetails", resolveResult.TenantIdOrName] ); } } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/TenantSettingValueProvider.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantSettingValueProvider.cs similarity index 93% rename from framework/src/Volo.Abp.Settings/Volo/Abp/Settings/TenantSettingValueProvider.cs rename to framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantSettingValueProvider.cs index 880be2709d..39c7803d52 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/TenantSettingValueProvider.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantSettingValueProvider.cs @@ -19,7 +19,7 @@ public class TenantSettingValueProvider : SettingValueProvider CurrentTenant = currentTenant; } - public override async Task GetOrNullAsync(SettingDefinition setting) + public async override Task GetOrNullAsync(SettingDefinition setting) { return await SettingStore.GetOrNullAsync(setting.Name, Name, CurrentTenant.Id?.ToString()); } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs index 2755b4d608..83429e36ce 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using JetBrains.Annotations; using Volo.Abp.Localization; @@ -74,7 +75,7 @@ public class EntityExtensionConfiguration [NotNull] public virtual ImmutableList GetProperties() { - return Properties.Values.ToImmutableList(); + return Properties.Values.OrderBy(t => t.UI.Order).ToImmutableList(); } private static void NormalizeProperty(ExtensionPropertyConfiguration propertyInfo) diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiConfiguration.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiConfiguration.cs index 38b47b71ad..3c4a0e921f 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiConfiguration.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiConfiguration.cs @@ -4,6 +4,8 @@ namespace Volo.Abp.ObjectExtending.Modularity; public class ExtensionPropertyUiConfiguration { + public const int DefaultOrder = 1000; + [NotNull] public ExtensionPropertyUiTableConfiguration OnTable { get; } @@ -15,6 +17,8 @@ public class ExtensionPropertyUiConfiguration [NotNull] public ExtensionPropertyLookupConfiguration Lookup { get; set; } + + public int Order { get; set; } public ExtensionPropertyUiConfiguration() { @@ -22,5 +26,6 @@ public class ExtensionPropertyUiConfiguration OnCreateForm = new ExtensionPropertyUiFormConfiguration(); OnEditForm = new ExtensionPropertyUiFormConfiguration(); Lookup = new ExtensionPropertyLookupConfiguration(); + Order = DefaultOrder; } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs index cdde4211e9..c1426e3833 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs @@ -174,6 +174,7 @@ public static class ModuleExtensionConfigurationHelper property.DefaultValue = propertyConfig.DefaultValue; property.DefaultValueFactory = propertyConfig.DefaultValueFactory; property.Lookup = propertyConfig.UI.Lookup; + property.UI.Order = propertyConfig.UI.Order; foreach (var configuration in propertyConfig.Configuration) { property.Configuration[configuration.Key] = configuration.Value; diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs index 6656e6127c..c7ebe2f961 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs @@ -69,8 +69,7 @@ public class ObjectExtensionInfo [NotNull] public virtual ImmutableList GetProperties() { - return Properties.OrderBy(t => t.Key) - .Select(t => t.Value) + return Properties.OrderBy(t => t.Value.UI.Order).Select(t => t.Value) .ToImmutableList(); } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs index 8c3a54414e..ab941a2f15 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -57,6 +57,8 @@ public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName, I [NotNull] public ExtensionPropertyLookupConfiguration Lookup { get; set; } + public ExtensionPropertyUI UI { get; set; } + public ObjectExtensionPropertyInfo( [NotNull] ObjectExtensionInfo objectExtension, [NotNull] Type type, @@ -73,10 +75,28 @@ public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName, I Attributes.AddRange(ExtensionPropertyHelper.GetDefaultAttributes(Type)); DefaultValue = TypeHelper.GetDefaultValue(Type); Lookup = new ExtensionPropertyLookupConfiguration(); + UI = new ExtensionPropertyUI(); } public object? GetDefaultValue() { return ExtensionPropertyHelper.GetDefaultValue(Type, DefaultValueFactory, DefaultValue); } + + public class ExtensionPropertyUI + { + public int Order { get; set; } + + public ExtensionPropertyUIEditModal EditModal { get; set; } + + public ExtensionPropertyUI() + { + EditModal = new ExtensionPropertyUIEditModal(); + } + } + + public class ExtensionPropertyUIEditModal + { + public bool IsReadOnly { get; set; } + } } diff --git a/framework/src/Volo.Abp.Settings/Volo.Abp.Settings.csproj b/framework/src/Volo.Abp.Settings/Volo.Abp.Settings.csproj index fdedd2fc69..524f932e9c 100644 --- a/framework/src/Volo.Abp.Settings/Volo.Abp.Settings.csproj +++ b/framework/src/Volo.Abp.Settings/Volo.Abp.Settings.csproj @@ -16,8 +16,8 @@ - + diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs index eb05fbb949..00c3d9e7e5 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs @@ -3,16 +3,16 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Localization; using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; using Volo.Abp.Security; +using Volo.Abp.Data; namespace Volo.Abp.Settings; [DependsOn( typeof(AbpLocalizationAbstractionsModule), typeof(AbpSecurityModule), - typeof(AbpMultiTenancyModule) - )] + typeof(AbpDataModule) +)] public class AbpSettingsModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) @@ -27,7 +27,6 @@ public class AbpSettingsModule : AbpModule options.ValueProviders.Add(); options.ValueProviders.Add(); options.ValueProviders.Add(); - options.ValueProviders.Add(); options.ValueProviders.Add(); }); } diff --git a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs index 3c27c640cd..0f5698ab08 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerUI; using Volo.Abp.Content; namespace Microsoft.Extensions.DependencyInjection; @@ -61,20 +62,70 @@ public static class AbpSwaggerGenServiceCollectionExtensions options.AddSecurityRequirement(new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { - new OpenApiSecurityScheme + Reference = new OpenApiReference { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "oauth2" - } - }, - Array.Empty() - } + Type = ReferenceType.SecurityScheme, + Id = "oauth2" + } + }, + Array.Empty() + } }); setupAction?.Invoke(options); }); } + + public static IServiceCollection AddAbpSwaggerGenWithOidc( + this IServiceCollection services, + [NotNull] string authority, + string[] scopes = null, + string[] flows = null, + string discoveryEndpoint = null, + Action setupAction = null) + { + var discoveryUrl = discoveryEndpoint != null ? + new Uri(discoveryEndpoint) : + new Uri($"{authority.TrimEnd('/')}/.well-known/openid-configuration"); + + flows ??= new [] { "authorization_code" }; + + services.Configure(swaggerUiOptions => + { + swaggerUiOptions.ConfigObject.AdditionalItems["oidcSupportedFlows"] = flows; + swaggerUiOptions.ConfigObject.AdditionalItems["oidcSupportedScopes"] = scopes; + swaggerUiOptions.ConfigObject.AdditionalItems["oidcDiscoveryEndpoint"] = discoveryEndpoint; + }); + + return services + .AddAbpSwaggerGen() + .AddSwaggerGen( + options => + { + options.AddSecurityDefinition("oidc", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.OpenIdConnect, + OpenIdConnectUrl = discoveryUrl + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "oidc" + } + }, + Array.Empty() + } + }); + setupAction?.Invoke(options); + }); + } } diff --git a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js index c143b7e75e..6edbe815be 100644 --- a/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js +++ b/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.swagger.js @@ -1,16 +1,20 @@ var abp = abp || {}; -(function() { +(function () { - abp.SwaggerUIBundle = function(configObject) { + abp.SwaggerUIBundle = function (configObject) { var excludeUrl = ["swagger.json", "connect/token"] var firstRequest = true; + var oidcSupportedFlows = configObject.oidcSupportedFlows || []; + var oidcSupportedScopes = configObject.oidcSupportedScopes || []; + var oidcDiscoveryEndpoint = configObject.oidcDiscoveryEndpoint || []; abp.appPath = configObject.baseUrl || abp.appPath; var requestInterceptor = configObject.requestInterceptor; + var responseInterceptor = configObject.responseInterceptor; - configObject.requestInterceptor = async function(request) { + configObject.requestInterceptor = async function (request) { if (request.url.includes(excludeUrl[1])) { firstRequest = true; @@ -22,6 +26,10 @@ var abp = abp || {}; }); firstRequest = false; } + // Intercept .well-known request when the discoveryEndpoint is provided + if (!firstRequest && oidcDiscoveryEndpoint.length !== 0 && request.url.includes(".well-known/openid-configuration")) { + request.url = oidcDiscoveryEndpoint; + } var antiForgeryToken = abp.security.antiForgery.getToken(); if (antiForgeryToken) { @@ -38,6 +46,31 @@ var abp = abp || {}; return request; }; + configObject.responseInterceptor = async function (response) { + if (response.url.endsWith(".well-known/openid-configuration") && response.status === 200) { + var openIdConnectData = JSON.parse(response.text); + + if (oidcDiscoveryEndpoint.length > 0) { + openIdConnectData.grant_types_supported = oidcSupportedFlows; + } + + if (oidcSupportedFlows.length > 0) { + openIdConnectData.grant_types_supported = oidcSupportedFlows; + } + + if (oidcSupportedScopes.length > 0) { + openIdConnectData.scopes_supported = oidcSupportedScopes; + } + + response.text = JSON.stringify(openIdConnectData); + } + + if (responseInterceptor) { + responseInterceptor(response); + } + return response; + }; + return SwaggerUIBundle(configObject); } })(); diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs index ba87dfb4ae..43cc829010 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs @@ -29,7 +29,7 @@ public class AbpTemplateRenderer : ITemplateRenderer, ITransientDependency string cultureName = null, Dictionary globalContext = null) { - var templateDefinition = TemplateDefinitionManager.Get(templateName); + var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName); var renderEngine = templateDefinition.RenderEngine; diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs index 3c98daa22b..28b2e28ad0 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -12,10 +12,13 @@ public class AbpTextTemplatingOptions public string DefaultRenderingEngine { get; set; } + public HashSet DeletedTemplates { get; } + public AbpTextTemplatingOptions() { DefinitionProviders = new TypeList(); ContentContributors = new TypeList(); RenderingEngines = new Dictionary(); + DeletedTemplates = new HashSet(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs new file mode 100644 index 0000000000..e275eb39be --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating; + +public interface IDynamicTemplateDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync(string name); +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs new file mode 100644 index 0000000000..d298a42541 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating; + +public interface IStaticTemplateDefinitionStore +{ + Task GetAsync([NotNull] string name); + + Task> GetAllAsync(); + + Task GetOrNullAsync(string name); +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs index 31af5d1da5..fa20673cea 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; +using System.Threading.Tasks; using JetBrains.Annotations; namespace Volo.Abp.TextTemplating; public interface ITemplateDefinitionManager { - [NotNull] - TemplateDefinition Get([NotNull] string name); + [ItemNotNull] + Task GetAsync([NotNull] string name); - [NotNull] - IReadOnlyList GetAll(); + [ItemNotNull] + Task> GetAllAsync(); - [CanBeNull] - TemplateDefinition GetOrNull(string name); + [ItemCanBeNull] + Task GetOrNullAsync(string name); } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs new file mode 100644 index 0000000000..326b525c0e --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating; + +public class NullIDynamicTemplateDefinitionStore : IDynamicTemplateDefinitionStore, ISingletonDependency +{ + private readonly static Task CachedTemplateResult = Task.FromResult((TemplateDefinition)null); + + private readonly static Task> CachedFeaturesResult = Task.FromResult((IReadOnlyList)Array.Empty().ToImmutableList()); + + public Task GetAsync(string name) + { + return CachedTemplateResult; + } + + public Task> GetAllAsync() + { + return CachedFeaturesResult; + } + + public Task GetOrNullAsync(string name) + { + return CachedTemplateResult; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs new file mode 100644 index 0000000000..d12db86da8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating; + +public class StaticTemplateDefinitionStore : IStaticTemplateDefinitionStore, ISingletonDependency +{ + protected Lazy> TemplateDefinitions { get; } + + protected AbpTextTemplatingOptions Options { get; } + + protected IServiceProvider ServiceProvider { get; } + + public StaticTemplateDefinitionStore(IOptions options, IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + + TemplateDefinitions = new Lazy>(CreateTextTemplateDefinitions, true); + } + + public virtual Task GetAsync(string name) + { + Check.NotNull(name, nameof(name)); + + var template = GetOrNullAsync(name); + + if (template == null) + { + throw new AbpException("Undefined template: " + name); + } + + return template; + } + + public virtual Task> GetAllAsync() + { + return Task.FromResult>(TemplateDefinitions.Value.Values.ToImmutableList()); + } + + public virtual Task GetOrNullAsync(string name) + { + return Task.FromResult(TemplateDefinitions.Value.GetOrDefault(name)); + } + + protected virtual IDictionary CreateTextTemplateDefinitions() + { + var templates = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) + .ToList(); + + var context = new TemplateDefinitionContext(templates); + + foreach (var provider in providers) + { + provider.PreDefine(context); + } + + foreach (var provider in providers) + { + provider.Define(context); + } + + foreach (var provider in providers) + { + provider.PostDefine(context); + } + } + + return templates; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs index 58ffcc491f..64ecd1f4c7 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -26,14 +26,14 @@ public class TemplateContentProvider : ITemplateContentProvider, ITransientDepen _templateDefinitionManager = templateDefinitionManager; } - public virtual Task GetContentOrNullAsync( + public virtual async Task GetContentOrNullAsync( [NotNull] string templateName, [CanBeNull] string cultureName = null, bool tryDefaults = true, bool useCurrentCultureIfCultureNameIsNull = true) { - var template = _templateDefinitionManager.Get(templateName); - return GetContentOrNullAsync(template, cultureName); + var template = await _templateDefinitionManager.GetAsync(templateName); + return await GetContentOrNullAsync(template, cultureName); } public virtual async Task GetContentOrNullAsync( diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs index d028f46702..64aeba815e 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -28,7 +28,7 @@ public class TemplateDefinition : IHasNameWithLocalizableDisplayName public string Layout { get; set; } [CanBeNull] - public Type LocalizationResource { get; set; } + public string LocalizationResourceName { get; set; } public bool IsInlineLocalized { get; set; } @@ -60,14 +60,26 @@ public class TemplateDefinition : IHasNameWithLocalizableDisplayName public TemplateDefinition( [NotNull] string name, - [CanBeNull] Type localizationResource = null, + [NotNull] Type localizationResource, + [CanBeNull] ILocalizableString displayName = null, + bool isLayout = false, + string layout = null, + string defaultCultureName = null) + : this(name, LocalizationResourceNameAttribute.GetName(localizationResource), displayName, isLayout, layout, defaultCultureName) + { + + } + + public TemplateDefinition( + [NotNull] string name, + [CanBeNull] string localizationResourceName = null, [CanBeNull] ILocalizableString displayName = null, bool isLayout = false, string layout = null, string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength); - LocalizationResource = localizationResource; + LocalizationResourceName = localizationResourceName; DisplayName = displayName ?? new FixedLocalizableString(Name); IsLayout = isLayout; Layout = layout; diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs index a282b5e5e2..7e2920e446 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -1,85 +1,50 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace Volo.Abp.TextTemplating; public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency { - protected Lazy> TemplateDefinitions { get; } + protected readonly IStaticTemplateDefinitionStore StaticStore; + protected readonly IDynamicTemplateDefinitionStore DynamicStore; - protected AbpTextTemplatingOptions Options { get; } - - protected IServiceProvider ServiceProvider { get; } - - public TemplateDefinitionManager( - IOptions options, - IServiceProvider serviceProvider) + public TemplateDefinitionManager(IStaticTemplateDefinitionStore staticStore, IDynamicTemplateDefinitionStore dynamicStore) { - ServiceProvider = serviceProvider; - Options = options.Value; - - TemplateDefinitions = - new Lazy>(CreateTextTemplateDefinitions, true); + StaticStore = staticStore; + DynamicStore = dynamicStore; } - public virtual TemplateDefinition Get(string name) + public virtual async Task GetAsync(string name) { - Check.NotNull(name, nameof(name)); - - var template = GetOrNull(name); - - if (template == null) + var permission = await GetOrNullAsync(name); + if (permission == null) { - throw new AbpException("Undefined template: " + name); + throw new AbpException("Undefined Template: " + name); } - return template; + return permission; } - public virtual IReadOnlyList GetAll() + public virtual async Task GetOrNullAsync(string name) { - return TemplateDefinitions.Value.Values.ToImmutableList(); - } + Check.NotNull(name, nameof(name)); - public virtual TemplateDefinition GetOrNull(string name) - { - return TemplateDefinitions.Value.GetOrDefault(name); + return await StaticStore.GetOrNullAsync(name) ?? await DynamicStore.GetOrNullAsync(name); } - protected virtual IDictionary CreateTextTemplateDefinitions() + public virtual async Task> GetAllAsync() { - var templates = new Dictionary(); - - using (var scope = ServiceProvider.CreateScope()) - { - var providers = Options - .DefinitionProviders - .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) - .ToList(); - - var context = new TemplateDefinitionContext(templates); + var staticTemplates = await StaticStore.GetAllAsync(); + var staticTemplateNames = staticTemplates + .Select(p => p.Name) + .ToImmutableHashSet(); - foreach (var provider in providers) - { - provider.PreDefine(context); - } - - foreach (var provider in providers) - { - provider.Define(context); - } - - foreach (var provider in providers) - { - provider.PostDefine(context); - } - } + var dynamicTemplates = await DynamicStore.GetAllAsync(); - return templates; + /* We prefer static Templates over dynamics */ + return staticTemplates.Concat(dynamicTemplates.Where(d => !staticTemplateNames.Contains(d.Name))).ToImmutableList(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs index f496a8fea8..a2c354801d 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs @@ -31,9 +31,9 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine protected virtual IStringLocalizer GetLocalizerOrNull(TemplateDefinition templateDefinition) { - if (templateDefinition.LocalizationResource != null) + if (templateDefinition.LocalizationResourceName != null) { - return StringLocalizerFactory.Create(templateDefinition.LocalizationResource); + return StringLocalizerFactory.CreateByResourceName(templateDefinition.LocalizationResourceName); } return StringLocalizerFactory.CreateDefaultOrNull(); diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs index 0ae297a136..27e400c082 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -5,10 +5,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles; public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader { + private IFileInfo _fileInfo; private string _content; public async Task ReadContentsAsync(IFileInfo fileInfo) { + _fileInfo = fileInfo; _content = await fileInfo.ReadAsStringAsync(); } @@ -21,4 +23,13 @@ public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentR return null; } + + public TemplateContentFileInfo GetFile() + { + return new TemplateContentFileInfo() + { + FileName = _fileInfo.Name, + FileContent = _content + }; + } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs new file mode 100644 index 0000000000..5469700432 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles; + +public class TemplateContentFileInfo +{ + public string FileName { get; set; } + + public string FileContent { get; set; } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs new file mode 100644 index 0000000000..0b049b07ab --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles; + +public class TemplateContentFileProvider : ITransientDependency +{ + protected IVirtualFileProvider VirtualFileProvider { get; } + + public TemplateContentFileProvider(IVirtualFileProvider virtualFileProvider) + { + VirtualFileProvider = virtualFileProvider; + } + + public async Task> GetFilesAsync(TemplateDefinition templateDefinition) + { + var files = new List(); + + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); + if (virtualPath == null) + { + return files; + } + + var fileInfo = VirtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + var directoryContents = VirtualFileProvider.GetDirectoryContents(virtualPath); + if (!directoryContents.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + fileInfo = new VirtualDirectoryFileInfo(virtualPath, virtualPath, DateTimeOffset.UtcNow); + } + + if (fileInfo.IsDirectory) + { + //TODO: Configure file extensions. + var folderReader = new VirtualFolderLocalizedTemplateContentReader(new[] { ".tpl", ".cshtml" }); + await folderReader.ReadContentsAsync(VirtualFileProvider, virtualPath); + files.AddRange(folderReader.GetFiles()); + } + else + { + var singleFileReader = new FileInfoLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + files.Add(singleFileReader.GetFile()); + } + + return files; + } +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs index 6ff24e4801..828978b1d2 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; using Volo.Abp.VirtualFileSystem; @@ -8,7 +9,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles; public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader { - private Dictionary _dictionary; + private Dictionary _dictionary; private readonly string[] _fileExtension; public VirtualFolderLocalizedTemplateContentReader(string[] fileExtension) @@ -20,7 +21,7 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon IVirtualFileProvider virtualFileProvider, string virtualPath) { - _dictionary = new Dictionary(); + _dictionary = new Dictionary(); var directoryContents = virtualFileProvider.GetDirectoryContents(virtualPath); if (!directoryContents.Exists) @@ -35,7 +36,11 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon continue; } - _dictionary.Add(file.Name.RemovePostFix(_fileExtension), await file.ReadAsStringAsync()); + _dictionary.Add(file.Name.RemovePostFix(_fileExtension), new TemplateContentFileInfo() + { + FileName = file.Name, + FileContent = await file.ReadAsStringAsync() + }); } } @@ -46,6 +51,11 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon return null; } - return _dictionary.GetOrDefault(cultureName); + return _dictionary.GetOrDefault(cultureName)?.FileContent; + } + + public List GetFiles() + { + return _dictionary.Values.ToList(); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs index 10312e1513..109547ada5 100644 --- a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs @@ -45,6 +45,11 @@ public class DefaultAbpCompiledViewProvider : IAbpCompiledViewProvider, ITransie } var templateContent = await _templateContentProvider.GetContentOrNullAsync(templateDefinition); + if (templateContent == null) + { + throw new AbpException($"Razor template content of {templateDefinition.Name} is null!"); + } + return CachedAssembles.GetOrAdd((templateDefinition.Name + templateContent).ToMd5(), await CreateAssembly(templateContent)); } diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs index 582ebaf907..86298f2daf 100644 --- a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs @@ -74,7 +74,7 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi Dictionary globalContext, object model = null) { - var templateDefinition = TemplateDefinitionManager.Get(templateName); + var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName); var renderedContent = await RenderSingleTemplateAsync( templateDefinition, diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs index 4612ec04df..a4da669893 100644 --- a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs @@ -61,7 +61,7 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran Dictionary globalContext, object model = null) { - var templateDefinition = TemplateDefinitionManager.Get(templateName); + var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName); var renderedContent = await RenderSingleTemplateAsync( templateDefinition, diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj b/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj index 657911f50d..65b07f1579 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj +++ b/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj @@ -22,6 +22,7 @@ + diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs index f1b6124c4d..9074943396 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs @@ -1,12 +1,13 @@ using Volo.Abp.Authorization; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.UI.Navigation.Localization.Resource; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.UI.Navigation; -[DependsOn(typeof(AbpUiModule), typeof(AbpAuthorizationModule))] +[DependsOn(typeof(AbpUiModule), typeof(AbpAuthorizationModule), typeof(AbpMultiTenancyModule))] public class AbpUiNavigationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index b9b39f2f28..c04393d104 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -75,7 +75,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency if (Options != null) { - throw new AbpException("This unit of work is already initialized before!"); + throw new AbpException("This unit of work has already been initialized."); } Options = _defaultOptions.Normalize(options.Clone()); @@ -84,7 +84,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual void Reserve(string reservationName) { - Check.NotNull(reservationName, nameof(reservationName)); + Check.NotNullOrWhiteSpace(reservationName, nameof(reservationName)); ReservationName = reservationName; IsReserved = true; @@ -188,12 +188,12 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual void AddDatabaseApi(string key, IDatabaseApi api) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(api, nameof(api)); if (_databaseApis.ContainsKey(key)) { - throw new AbpException("There is already a database API in this unit of work with given key."); + throw new AbpException("This unit of work already contains a database API for the given key."); } _databaseApis.Add(key, api); @@ -201,7 +201,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual IDatabaseApi GetOrAddDatabaseApi(string key, Func factory) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(factory, nameof(factory)); return _databaseApis.GetOrAdd(key, factory); @@ -209,19 +209,19 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual ITransactionApi? FindTransactionApi(string key) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); return _transactionApis.GetOrDefault(key); } public virtual void AddTransactionApi(string key, ITransactionApi api) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(api, nameof(api)); if (_transactionApis.ContainsKey(key)) { - throw new AbpException("There is already a transaction API in this unit of work with given key."); + throw new AbpException("This unit of work already contains a transaction API for the given key."); } _transactionApis.Add(key, api); @@ -229,7 +229,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency public virtual ITransactionApi GetOrAddTransactionApi(string key, Func factory) { - Check.NotNull(key, nameof(key)); + Check.NotNullOrWhiteSpace(key, nameof(key)); Check.NotNull(factory, nameof(factory)); return _transactionApis.GetOrAdd(key, factory); @@ -332,7 +332,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency { if (IsCompleted || _isCompleting) { - throw new AbpException("Complete is called before!"); + throw new AbpException("Completion has already been requested for this unit of work."); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProviderController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProviderController.cs new file mode 100644 index 0000000000..185d206dc6 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProviderController.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Tracing; + +namespace Volo.Abp.AspNetCore.CorrelationIdProvider; + +[Route("api/correlation")] +public class CorrelationIdProviderController : AbpController +{ + [HttpGet] + [Route("get")] + public string Get() + { + return this.HttpContext.RequestServices.GetRequiredService().Get(); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProvider_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProvider_Tests.cs new file mode 100644 index 0000000000..0db761f57d --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProvider_Tests.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc; +using Xunit; + +namespace Volo.Abp.AspNetCore.CorrelationIdProvider; + +public class CorrelationIdProvider_Tests : AspNetCoreMvcTestBase +{ + [Fact] + public async Task Test() + { + // Test AbpCorrelationIdMiddleware without X-Correlation-Id header + using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/correlation/404")) + { + var response = await Client.SendAsync(requestMessage); + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + + response.Headers.ShouldContain(x => x.Key == "X-Correlation-Id" && x.Value.First() != null); + } + + var correlationId = Guid.NewGuid().ToString("N"); + + // Test AbpCorrelationIdMiddleware + using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/correlation/404")) + { + requestMessage.Headers.Add("X-Correlation-Id", correlationId); + var response = await Client.SendAsync(requestMessage); + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + + response.Headers.ShouldContain(x => x.Key == "X-Correlation-Id" && x.Value.First() == correlationId); + } + + // Test AspNetCoreCorrelationIdProvider + using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/correlation/get")) + { + requestMessage.Headers.Add("X-Correlation-Id", correlationId); + var response = await Client.SendAsync(requestMessage); + response.StatusCode.ShouldBe(HttpStatusCode.OK); + + (await response.Content.ReadAsStringAsync()).ShouldBe(correlationId); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index d8a49abf23..6051ae6520 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -88,6 +88,8 @@ public class AbpAspNetCoreMvcTestModule : AbpModule ? "phones" : urlActionNameNormalizerContext.ActionNameInUrl; }); + + options.ExposeIntegrationServices = true; }); Configure(options => diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonAuditSerializer_Test.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonAuditSerializer_Test.cs index eb9dec3b5e..648325862a 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonAuditSerializer_Test.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonAuditSerializer_Test.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; @@ -58,7 +57,6 @@ public class JsonAuditSerializer_Test : AbpAuditingTestBase [DisableAuditing] public string Password { get; set; } - [JsonIgnore] public string PrivateEmail { get; set; } public DateTime Birthday { get; set; } diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/CorrelationIdProvider/CorrelationIdProvider_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/CorrelationIdProvider/CorrelationIdProvider_Tests.cs new file mode 100644 index 0000000000..997038afa1 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/CorrelationIdProvider/CorrelationIdProvider_Tests.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.Tracing; +using Xunit; + +namespace Volo.Abp.CorrelationIdProvider; + +public class CorrelationIdProvider_Tests +{ + [Fact] + public async Task Test() + { + using (var application = await AbpApplicationFactory.CreateAsync()) + { + await application.InitializeAsync(); + + var correlationIdProvider = application.ServiceProvider.GetRequiredService(); + + correlationIdProvider.Get().ShouldBeNull(); + + var correlationId = Guid.NewGuid().ToString("N"); + using (correlationIdProvider.Change(correlationId)) + { + correlationIdProvider.Get().ShouldBe(correlationId); + } + + correlationIdProvider.Get().ShouldBeNull(); + } + } +} diff --git a/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentUserTenantResolveContributor_Tests.cs b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentUserTenantResolveContributor_Tests.cs new file mode 100644 index 0000000000..b865dc3780 --- /dev/null +++ b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/CurrentUserTenantResolveContributor_Tests.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.Settings; +using Xunit; + +namespace Volo.Abp.MultiTenancy; + +public class CurrentUserTenantResolveContributor_Tests : MultiTenancyTestBase +{ + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.TenantResolvers.Add(new TestTenantResolveContributor()); + }); + } + + [Fact] + public void CurrentUserTenantResolveContributor_Should_Add_First() + { + var options = GetRequiredService>().Value; + options.TenantResolvers.First().GetType().ShouldBe(typeof(CurrentUserTenantResolveContributor)); + } + + class TestTenantResolveContributor : ITenantResolveContributor + { + public string Name { get; } + + public Task ResolveAsync(ITenantResolveContext context) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyResource_Tests.cs b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyResource_Tests.cs new file mode 100644 index 0000000000..c09b7b9507 --- /dev/null +++ b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/MultiTenancyResource_Tests.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Localization; +using Shouldly; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy.Localization; +using Xunit; + +namespace Volo.Abp.MultiTenancy; + +public class MultiTenancyResource_Tests : MultiTenancyTestBase +{ + [Fact] + public void MultiTenancyResource_Test() + { + var q = GetRequiredService>(); + using (CultureHelper.Use("en")) + { + GetRequiredService>()["TenantNotFoundMessage"].Value.ShouldBe("Tenant not found!"); + } + + using (CultureHelper.Use("tr")) + { + GetRequiredService>()["TenantNotFoundMessage"].Value.ShouldBe("Kiracı bulunamadı!"); + } + } +} diff --git a/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/TenantSettingValueProvider_Tests.cs b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/TenantSettingValueProvider_Tests.cs new file mode 100644 index 0000000000..dfdbded93c --- /dev/null +++ b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo/Abp/MultiTenancy/TenantSettingValueProvider_Tests.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.Settings; +using Xunit; + +namespace Volo.Abp.MultiTenancy; + +public class TenantSettingValueProvider_Tests : MultiTenancyTestBase +{ + [Fact] + public void TenantSettingValueProvider_Should_Add_Correction() + { + var options = GetRequiredService>().Value; + + options.ValueProviders[0].ShouldBe(typeof(DefaultValueSettingValueProvider)); + options.ValueProviders[1].ShouldBe(typeof(ConfigurationSettingValueProvider)); + options.ValueProviders[2].ShouldBe(typeof(GlobalSettingValueProvider)); + options.ValueProviders[3].ShouldBe(typeof(TenantSettingValueProvider)); + options.ValueProviders[4].ShouldBe(typeof(UserSettingValueProvider)); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs index 6a0edcb3f5..fada3effe7 100644 --- a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs @@ -38,7 +38,7 @@ public class AbpCompiledViewProviderOptions_Tests : TemplateDefinitionTests +{ + [Fact] + public async Task GetRazorFilesAsync() + { + var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail); + var files = await TemplateContentFileProvider.GetFilesAsync(definition); + files.Count.ShouldBe(2); + files.ShouldContain(x => x.FileName == "en.cshtml" && x.FileContent.Contains("Welcome @Model.Name to the abp.io!")); + files.ShouldContain(x => x.FileName == "tr.cshtml" && x.FileContent.Contains("Merhaba @Model.Name, abp.io'ya hoşgeldiniz!")); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs new file mode 100644 index 0000000000..674173313c --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.TextTemplating.VirtualFiles; +using Xunit; + +namespace Volo.Abp.TextTemplating.Scriban.SampleTemplates; + +public class ScribanTemplateContentFileProvider_Tests : TemplateContentFileProvider_Tests +{ + [Fact] + public async Task GetScribanFilesAsync() + { + var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail); + var files = await TemplateContentFileProvider.GetFilesAsync(definition); + files.Count.ShouldBe(2); + files.ShouldContain(x => x.FileName == "en.tpl" && x.FileContent.Contains("Welcome {{model.name}} to the abp.io!")); + files.ShouldContain(x => x.FileName == "tr.tpl" && x.FileContent.Contains("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!")); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs index 036080cc26..02ce27d2b7 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -1,4 +1,5 @@ -using Shouldly; +using System.Threading.Tasks; +using Shouldly; using Volo.Abp.Modularity; using Xunit; @@ -15,28 +16,28 @@ public abstract class TemplateDefinitionTests : AbpTextTemplatin } [Fact] - public void Should_Retrieve_Template_Definition_By_Name() + public async Task Should_Retrieve_Template_Definition_By_Name() { - var welcomeEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail); + var welcomeEmailTemplate = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail); welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail); welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse(); - var forgotPasswordEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail); + var forgotPasswordEmailTemplate = await TemplateDefinitionManager.GetAsync(TestTemplates.ForgotPasswordEmail); forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail); forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue(); } [Fact] - public void Should_Get_Null_If_Template_Not_Found() + public async Task Should_Get_Null_If_Template_Not_Found() { - var definition = TemplateDefinitionManager.GetOrNull("undefined-template"); + var definition = await TemplateDefinitionManager.GetOrNullAsync("undefined-template"); definition.ShouldBeNull(); } [Fact] - public void Should_Retrieve_All_Template_Definitions() + public async Task Should_Retrieve_All_Template_Definitions() { - var definitions = TemplateDefinitionManager.GetAll(); + var definitions = await TemplateDefinitionManager.GetAllAsync(); definitions.Count.ShouldBeGreaterThan(1); } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs index 7ba1850d9c..aee41db4be 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs @@ -24,7 +24,7 @@ public abstract class LocalizedTemplateContentReaderFactory_Tests : AbpTextTemplatingTestBase + where TStartupModule : IAbpModule +{ + protected readonly TemplateContentFileProvider TemplateContentFileProvider; + protected readonly ITemplateDefinitionManager TemplateDefinitionManager; + + protected TemplateContentFileProvider_Tests() + { + TemplateContentFileProvider = GetRequiredService(); + TemplateDefinitionManager = GetRequiredService(); + } + + [Fact] + public async Task GetFilesAsync() + { + var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.HybridTemplateScriban); + + var files = await TemplateContentFileProvider.GetFilesAsync(definition); + files.Count.ShouldBe(1); + files.ShouldContain(x => x.FileName == "TestScribanTemplate.tpl" && x.FileContent == "Hello {{model.name}}, {{L \"HowAreYou\" }}"); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 89dc93051c..f2f0cdafef 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -24,13 +24,13 @@ public abstract class VirtualFileTemplateContributor_Tests : Abp public async Task Should_Get_Localized_Content_By_Culture() { (await VirtualFileTemplateContentContributor.GetOrNullAsync( - new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail), + new TemplateContentContributorContext(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail), ServiceProvider, "en"))) .ShouldBe(WelcomeEmailEnglishContent); (await VirtualFileTemplateContentContributor.GetOrNullAsync( - new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail), + new TemplateContentContributorContext(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail), ServiceProvider, "tr"))) .ShouldBe(WelcomeEmailTurkishContent); @@ -41,7 +41,7 @@ public abstract class VirtualFileTemplateContributor_Tests : Abp { (await VirtualFileTemplateContentContributor.GetOrNullAsync( new TemplateContentContributorContext( - TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail), + await TemplateDefinitionManager.GetAsync(TestTemplates.ForgotPasswordEmail), ServiceProvider, null))) .ShouldBe(ForgotPasswordEmailEnglishContent); diff --git a/modules/account/NuGet.md b/modules/account/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/modules/account/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index 316323a5cd..f9489f7797 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -144,7 +144,7 @@ public class RegisterModel : AccountPageModel ))).CheckErrors(); } - await SignInManager.SignInAsync(user, isPersistent: true); + await SignInManager.SignInAsync(user, isPersistent: true, ExternalLoginAuthSchema); } protected virtual async Task CheckSelfRegistrationAsync() diff --git a/modules/audit-logging/NuGet.md b/modules/audit-logging/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/modules/audit-logging/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/modules/background-jobs/NuGet.md b/modules/background-jobs/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/modules/background-jobs/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj index acad01b8e9..980afb22d5 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj @@ -6,7 +6,7 @@ - + diff --git a/modules/basic-theme/NuGet.md b/modules/basic-theme/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/modules/basic-theme/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs index f20432c665..a983d239e3 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/AbpAspNetCoreComponentsWebBasicThemeModule.cs @@ -1,12 +1,24 @@ using Volo.Abp.AspNetCore.Components.Web.Theming; +using Volo.Abp.AspNetCore.Components.Web.Theming.Theming; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme; [DependsOn( typeof(AbpAspNetCoreComponentsWebThemingModule) - )] +)] public class AbpAspNetCoreComponentsWebBasicThemeModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Themes.Add(); -} + if (options.DefaultThemeName == null) + { + options.DefaultThemeName = BasicTheme.Name; + } + }); + } +} \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs new file mode 100644 index 0000000000..2017cc8001 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/BasicTheme.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic; +using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; +using Volo.Abp.AspNetCore.Components.Web.Theming.Theming; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme; + +[ThemeName(Name)] +public class BasicTheme : ITheme, ITransientDependency +{ + public const string Name = "Basic"; + + public virtual Type GetLayout(string name, bool fallbackToDefault = true) + { + switch (name) + { + case StandardLayouts.Application: + case StandardLayouts.Account: + case StandardLayouts.Empty: + return typeof(MainLayout); + default: + return fallbackToDefault ? typeof(MainLayout) : typeof(NullLayout); + } + } +} \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor new file mode 100644 index 0000000000..4e04080071 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NullLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body \ No newline at end of file diff --git a/modules/blob-storing-database/NuGet.md b/modules/blob-storing-database/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/modules/blob-storing-database/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.abpmdl.json b/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.abpmdl.json index 81e2ef10f4..ed056b6014 100644 --- a/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.abpmdl.json +++ b/modules/blob-storing-database/Volo.Abp.BlobStoring.Database.abpmdl.json @@ -18,7 +18,7 @@ "BlobStoring.Database.Host.ConsoleApp.ConsoleApp": { "path": "host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.abppkg.json", "folder": "host/BlobStoring.Database.Host.ConsoleApp/src" - }i + }, "Volo.Abp.BlobStoring.Database.Domain": { "path": "src/Volo.Abp.BlobStoring.Database.Domain/Volo.Abp.BlobStoring.Database.Domain.abppkg.json", "folder": "src" diff --git a/modules/blogging/NuGet.md b/modules/blogging/NuGet.md new file mode 100644 index 0000000000..1073e17b8c --- /dev/null +++ b/modules/blogging/NuGet.md @@ -0,0 +1,131 @@ +## ℹ️ Description +This package is a part of the [ABP Framework](https://abp.io). + +### ABP — Open Source Web Application Framework for ASP.NET Core +ABP offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET and the ASP.NET Core platforms. ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. It provides the fundamental infrastructure, production-ready startup templates, application modules, UI themes, tooling, guides and documentation. + + +### For more information, check out the below links: + +🔗Official Website: https://abp.io + +🔗GitHub Repository: https://github.com/abpframework/abp + +🔗Samples: https://docs.abp.io/en/abp/latest/Samples/Index + +🔗Official Theme: https://www.LeptonTheme.com + +🔗Documentation: https://docs.abp.io + +🔗Community: https://community.abp.io + +🔗Blog: https://blog.abp.io + +🔗Books: https://abp.io/books + +🔗Twitter: https://twitter.com/abpframework + +🔗Discord: https://community.abp.io/discord + +🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp + +🔗YouTube: https://www.youtube.com/@Volosoft + + +## 🚀 Key Features of the ABP Framework + +🟡 Modularity + +🟡 Multi-Tenancy + +🟡 Bootstrap Tag Helpers + +🟡 Dynamic Forms + +🟡 Authentication + +🟡 Authorization + +🟡 Distributed Event Bus + +🟡 BLOB Storing + +🟡 Text Templating + +🟡 Tooling: ABP CLI + +🟡 Cross-Cutting Concerns + +🟡 Bundling & Minification + +🟡 Virtual File System + +🟡 Theming + +🟡 Background Jobs + +🟡 DDD Infrastructure + +🟡 Auto REST APIs + +🟡 Dynamic Client Proxies + +🟡 Multiple Database Providers + +🟡 Data filtering + +🟡 Test Infrastructure + +🟡 Audit Logging + +🟡 Object to Object Mapping + +🟡 Email & SMS Abstractions + +🟡 Localization + +🟡 Setting Management + +🟡 Extension Methods + +🟡 Aspect Oriented Programming + +🟡 Dependency Injection + + +### 📘 Supported Database Providers + +🔵 Entity Framework Core + +🔵 MongoDB + +🔵 Dapper + + +### 🎴 Supported UI Frameworks + +🔵 Angular + +🔵 Razor Pages + +🔵 Blazor Web Assembly + +🔵 Blazor Server + +🔵 MAUI with Blazor Hybrid + + +## 📫 Bug & Support + +Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues). + + +## 🏦 Go Commercial + +If you are building an enterprise-level application with pro support, extra tools like a code generator, fancy themes and a microservice template, time to invest in the commercial version! + +To understand how it works, see [How it Works]( https://commercial.abp.io/how-it-works) page. + +If you wonder why you should choose ABP Commercial, check out [Why ABP.IO Platform](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) page. + +Official website: [https://commercial.abp.io](https://commercial.abp.io/) diff --git a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs index fc53298f38..6f79fde6ad 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Startup.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Volo.Abp; +using Volo.Abp.Localization; namespace Volo.BloggingTestApp { @@ -12,6 +13,22 @@ namespace Volo.BloggingTestApp public void ConfigureServices(IServiceCollection services) { services.AddApplication(); + + services.Configure(options => + { + options.Languages.Add(new LanguageInfo("ar", "ar", "العربية", "ae")); + options.Languages.Add(new LanguageInfo("en", "en", "English", "gb")); + options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština", "cz")); + options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish", "fi")); + options.Languages.Add(new LanguageInfo("fr", "fr", "Français", "fr")); + options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak", "sk")); + options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi", "in")); + options.Languages.Add(new LanguageInfo("it", "it", "Italiano", "it")); + options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe", "tr")); + options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português", "br")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文", "cn")); + options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁体中文", "cn")); + }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs index ee379959dc..a65d92db30 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs @@ -32,7 +32,7 @@ namespace Volo.Blogging.Blogs Check.NotNullOrWhiteSpace(shortName, nameof(shortName)); var blog = await BlogRepository.FindByShortNameAsync(shortName); - + if (blog == null) { throw new EntityNotFoundException(typeof(Blog), shortName); diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json index a5499b77a5..d8b69815c7 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json @@ -19,12 +19,19 @@ "ReplyTo": "Reply To {0}", "ContinueReading": "Continue Reading", "DaysAgo": "{0} days ago", + "DayAgo": "{0} day ago", "YearsAgo": "{0} years ago", + "YearAgo": "{0} year ago", "MonthsAgo": "{0} months ago", + "MonthAgo": "{0} month ago", "WeeksAgo": "{0} weeks ago", + "WeekAgo": "{0} week ago", "MinutesAgo": "{0} minutes ago", + "MinuteAgo": "{0} minute ago", "SecondsAgo": "{0} seconds ago", + "SecondAgo": "{0} second ago", "HoursAgo": "{0} hours ago", + "HourAgo": "{0} hour ago", "Now": "now", "Content": "Content", "SeeAll": "See All", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json index fbfe898e3e..ff96ea4e62 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json @@ -18,13 +18,20 @@ "Reply": "Yanıtla", "ReplyTo": "{0}'a cevap ver", "ContinueReading": "Devamı...", - "DaysAgo": "{0} Gün Önce", + "DaysAgo": "{0} gün önce", + "DayAgo": "{0} gün önce", "YearsAgo": "{0} yıl önce", + "YearAgo": "{0} yıl önce", "MonthsAgo": "{0} ay önce", + "MonthAgo": "{0} ay önce", "WeeksAgo": "{0} hafta önce", + "WeekAgo": "{0} hafta önce", "MinutesAgo": "{0} dakika önce", + "MinuteAgo": "{0} dakika önce", "SecondsAgo": "{0} saniye önce", + "SecondAgo": "{0} saniye önce", "HoursAgo": "{0} saat önce", + "HourAgo": "{0} saat önce", "Now": "şimdi", "Content": "İçerik", "SeeAll": "Hepsini Gör", diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPageHelper.cs similarity index 69% rename from modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs rename to modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPageHelper.cs index df738fa83d..10047fcb2b 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPage.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/BloggingPageHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -6,6 +7,7 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.Localization; using Markdig; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; using Volo.Blogging.Localization; namespace Volo.Blogging.Pages.Blog @@ -96,45 +98,42 @@ namespace Volo.Blogging.Pages.Blog .Build()); } - public LocalizedHtmlString ConvertDatetimeToTimeAgo(DateTime dt) + public LocalizedHtmlString GetLocalizedTimeAgoText(DateTime dt) { var timeDiff = DateTime.Now - dt; var diffInDays = (int) timeDiff.TotalDays; - if (diffInDays >= 365) + switch (diffInDays) { - return L["YearsAgo", diffInDays / 365]; - } - if (diffInDays >= 30) - { - return L["MonthsAgo", diffInDays / 30]; - } - if (diffInDays >= 7) - { - return L["WeeksAgo", diffInDays / 7]; - } - if (diffInDays >= 1) - { - return L["DaysAgo", diffInDays]; + case >= 365: + return GetLocalizedTextAsSingularOrPlural("YearsAgo", "YearAgo", diffInDays / 365); + case >= 30: + return GetLocalizedTextAsSingularOrPlural("MonthsAgo", "MonthAgo", diffInDays / 30); + case >= 7: + return GetLocalizedTextAsSingularOrPlural("WeeksAgo", "WeekAgo", diffInDays / 7); + case >= 1: + return GetLocalizedTextAsSingularOrPlural("DaysAgo", "DayAgo", diffInDays); } var diffInSeconds = (int) timeDiff.TotalSeconds; - if (diffInSeconds >= 3600) + switch (diffInSeconds) { - return L["HoursAgo", diffInSeconds / 3600]; + case >= 3600: + return GetLocalizedTextAsSingularOrPlural("HoursAgo", "HourAgo", diffInSeconds / 3600); + case >= 60: + return GetLocalizedTextAsSingularOrPlural("MinutesAgo", "MinuteAgo", diffInSeconds / 60); + case >= 1: + return GetLocalizedTextAsSingularOrPlural("SecondsAgo", "SecondAgo", diffInSeconds); + default: + return L["Now"]; } - if (diffInSeconds >= 60) - { - return L["MinutesAgo", diffInSeconds / 60]; - } - if (diffInSeconds >= 1) - { - return L["SecondsAgo", diffInSeconds]; - } - - return L["Now"]; + } + + protected virtual LocalizedHtmlString GetLocalizedTextAsSingularOrPlural(string pluralKey, string singularKey, int value) + { + return value != 1 ? L[pluralKey, value] : L[singularKey, value]; } } } diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml index 1ebf445f05..09ab99a121 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml @@ -79,7 +79,7 @@ @(Model.Post.Writer.UserName) - @BloggingPageHelper.ConvertDatetimeToTimeAgo(Model.Post.CreationTime) + @BloggingPageHelper.GetLocalizedTimeAgoText(Model.Post.CreationTime) } @@ -147,27 +147,32 @@ }
-
- - @if (Model.Post.Writer.Name != null && Model.Post.Writer.Surname != null) - { -

@Model.Post.Writer.Name @Model.Post.Writer.Surname

- } - else + @if (Model.Post.Writer != null) + { +
+ + + @if (Model.Post.Writer.Name != null && Model.Post.Writer.Surname != null) + { +

@Model.Post.Writer.Name @Model.Post.Writer.Surname

+ } + else + { +

@Model.Post.Writer.UserName

+ } +
+
+
+ @if (Model.Post.Writer.JobTitle != null) { -

@Model.Post.Writer.UserName

+

@Model.Post.Writer.JobTitle

} - -
-
- @if (Model.Post.Writer.JobTitle != null) - { -

@Model.Post.Writer.JobTitle

- } -
- @if (CurrentUser.UserName == Model.Post.Writer.UserName) +
+ } + + @if (CurrentUser.Id.HasValue && CurrentUser.Id.Value == Model.Post.Writer?.Id) { - @L["EditProfile"] + @L["EditProfile"] } @if (Model.LatestPosts.Count > 1) @@ -185,23 +190,24 @@