Browse Source

Merge branch 'abpframework:dev' into dev

pull/12869/head
Nader Javid 4 years ago
committed by GitHub
parent
commit
4d0826b7dc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      .github/workflows/auto-pr.yml
  2. 11
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  3. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  4. 8
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  5. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en-GB.json
  6. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  7. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json
  8. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  9. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json
  10. 1
      build/common.ps1
  11. 2
      common.props
  12. 17
      docs/en/Background-Jobs.md
  13. 7
      docs/en/Background-Workers.md
  14. 258
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md
  15. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/app-nolayers-get-started.png
  16. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/community-talks-2022.4.png
  17. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/cover-53.png
  18. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/deployment-documentation.png
  19. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-personal-data-page.png
  20. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-user-menu.png
  21. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/leptonx-lite-documentations.png
  22. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/oauth-external-provider.gif
  23. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-new-question.png
  24. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-question-example.png
  25. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-questions.png
  26. BIN
      docs/en/Blog-Posts/2022-05-09 v5_3_Preview/webinar.png
  27. 3
      docs/en/CLI.md
  28. 6
      docs/en/Caching.md
  29. 54
      docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md
  30. 4
      docs/en/Contribution/Index.md
  31. 2
      docs/en/Dependency-Injection.md
  32. 99
      docs/en/Deployment/Clustered-Environment.md
  33. 9
      docs/en/Deployment/Index.md
  34. 219
      docs/en/Distributed-Event-Bus.md
  35. 14
      docs/en/Distributed-Locking.md
  36. 4
      docs/en/Getting-Started-Running-Solution.md
  37. 44
      docs/en/Local-Event-Bus.md
  38. 4
      docs/en/Migration-Guides/Abp-5-0-Blazor.md
  39. 2
      docs/en/Migration-Guides/Abp-5_0-Angular.md
  40. 11
      docs/en/Migration-Guides/Abp-5_3.md
  41. 1
      docs/en/Migration-Guides/Index.md
  42. 240
      docs/en/Modules/OpenIddict.md
  43. 2
      docs/en/Redis-Cache.md
  44. 4
      docs/en/Repositories.md
  45. 82
      docs/en/Themes/LeptonXLite/angular.md
  46. 147
      docs/en/Themes/LeptonXLite/blazor.md
  47. 74
      docs/en/Themes/LeptonXLite/mvc.md
  48. 29
      docs/en/UI/Angular/DateTime-Format-Pipe.md
  49. 12
      docs/en/UI/Angular/Page-Toolbar-Extensions.md
  50. BIN
      docs/en/_resources/Diagrams.pptx
  51. 48
      docs/en/docs-nav.json
  52. BIN
      docs/en/images/deployment-clustered.png
  53. BIN
      docs/en/images/deployment-single-instance.png
  54. 248
      docs/zh-Hans/CLI-New-Command-Samples.md
  55. 4
      docs/zh-Hans/CLI.md
  56. 2
      docs/zh-Hans/Connection-Strings.md
  57. 99
      docs/zh-Hans/Deployment/Clustered-Environment.md
  58. 9
      docs/zh-Hans/Deployment/Index.md
  59. 2
      docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md
  60. 2
      docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md
  61. 110
      docs/zh-Hans/Distributed-Locking.md
  62. 5
      docs/zh-Hans/Emailing.md
  63. 2
      docs/zh-Hans/Entities.md
  64. 6
      docs/zh-Hans/Settings.md
  65. 28
      docs/zh-Hans/docs-nav.json
  66. BIN
      docs/zh-Hans/images/deployment-clustered.png
  67. BIN
      docs/zh-Hans/images/deployment-single-instance.png
  68. BIN
      docs/zh-Hans/images/email-settings.png
  69. 16
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  70. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json
  71. 8
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  72. 100
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs
  73. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  74. 41
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  75. 43
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/DotnetEfToolManager.cs
  76. 111
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs
  77. 28
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MoveFileStep.cs
  78. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveFileStep.cs
  79. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs
  80. 40
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs
  81. 32
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  82. 25
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs
  83. 25
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs
  84. 25
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs
  85. 25
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs
  86. 24
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs
  87. 25
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs
  88. 25
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs
  89. 42
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  90. 2
      framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs
  91. 11
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs
  92. 31
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs
  93. 67
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs
  94. 18
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs
  95. 108
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  96. 23
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs
  97. 6
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs
  98. 11
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs
  99. 2
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs
  100. 6
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs

16
.github/workflows/auto-pr.yml

@ -1,10 +1,10 @@
name: Merge branch dev with rel-5.2 name: Merge branch dev with rel-5.3
on: on:
push: push:
branches: branches:
- rel-5.2 - rel-5.3
jobs: jobs:
merge-dev-with-rel-5-2: merge-rel-5-3-with-rel-5-2:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -12,13 +12,13 @@ jobs:
ref: dev ref: dev
- name: Reset promotion branch - name: Reset promotion branch
run: | run: |
git fetch origin rel-5.2:rel-5.2 git fetch origin rel-5.3:rel-5.3
git reset --hard rel-5.2 git reset --hard rel-5.3
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@v3
with: with:
branch: auto-merge/rel-5-2/${{github.run_number}} branch: auto-merge/rel-5-3/${{github.run_number}}
title: Merge branch dev with rel-5.2 title: Merge branch dev with rel-5.3
body: This PR generated automatically to merge dev with rel-5.2. Please review the changed files before merging to prevent any errors that may occur. body: This PR generated automatically to merge dev with rel-5.3. Please review the changed files before merging to prevent any errors that may occur.
reviewers: ${{github.actor}} reviewers: ${{github.actor}}
token: ${{ github.token }} token: ${{ github.token }}

11
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -384,6 +384,15 @@
"ShowBetweenDayCount": "Show Between Days", "ShowBetweenDayCount": "Show Between Days",
"PurchaseOrder": "Purchase Order", "PurchaseOrder": "Purchase Order",
"ShowCreateInvoiceOfOrganization": "Create Invoice", "ShowCreateInvoiceOfOrganization": "Create Invoice",
"ShowCreateQuotationOfOrganization": "Create Quotation" "ShowCreateQuotationOfOrganization": "Create Quotation",
"BookDiscounts": "Book Discounts",
"Permission:BookDiscount": "Book Discount",
"Menu:BookDiscounts": "Book Discounts",
"BookType": "Book Type",
"PurchasePlatform": "Purchase Platform",
"StartTime": "Start Time",
"EndTime": "End Time",
"CreateABookDiscount": "Create a book discount",
"BookDiscountDeletionConfirmationMessage": "Are you sure you want to delete this book discount?"
} }
} }

6
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -121,6 +121,10 @@
"MasteringAbpFrameworkBook": "Book: Mastering ABP Framework", "MasteringAbpFrameworkBook": "Book: Mastering ABP Framework",
"ABPIO-CommonPreferenceDefinition": "Get the latest news about ABP Platform like new posts, events and more.", "ABPIO-CommonPreferenceDefinition": "Get the latest news about ABP Platform like new posts, events and more.",
"BuiltOn": "Built-on", "BuiltOn": "Built-on",
"AbpFramework": "ABP Framework" "AbpFramework": "ABP Framework",
"Volo.AbpIo.Domain:080001": "Start Time can not be greater than End Time",
"Enum:BookType:0": "Mastering ABP Framework",
"Enum:PurchasePlatform:0": "Amazon",
"Enum:PurchasePlatform:1": "Packt"
} }
} }

8
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json

@ -49,7 +49,7 @@
"IndexPageHeroSection": "<span class=\"first-line\">A complete</span><span class=\"second-line\"> web development platform</span><span class=\"third-line\">built-on <img src=\"{0}\" width=\"110\" class=\"ml-1\" /> framework</span>", "IndexPageHeroSection": "<span class=\"first-line\">A complete</span><span class=\"second-line\"> web development platform</span><span class=\"third-line\">built-on <img src=\"{0}\" width=\"110\" class=\"ml-1\" /> framework</span>",
"AbpCommercialShortDescription": "ABP Commercial provides pre-built application modules, rapid application development tooling, professional UI themes, premium support and more.", "AbpCommercialShortDescription": "ABP Commercial provides pre-built application modules, rapid application development tooling, professional UI themes, premium support and more.",
"LiveDemo": "Live Demo", "LiveDemo": "Live Demo",
"GetLicence": "Get a Licence", "GetLicence": "Get a License",
"Application": "Application", "Application": "Application",
"StartupTemplates": "Startup Templates", "StartupTemplates": "Startup Templates",
"Startup": "Startup", "Startup": "Startup",
@ -199,6 +199,7 @@
"WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.", "WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.",
"WhatHappensWhenLicenseEndsExplanation6": "You can not get the <a href=\"{0}\">premium support</a> anymore.", "WhatHappensWhenLicenseEndsExplanation6": "You can not get the <a href=\"{0}\">premium support</a> anymore.",
"WhatHappensWhenLicenseEndsExplanation7": "You can extend (renew) your license if you want to continue getting these benefits. If you extend your license within <strong>1 month</strong> after your license expires, the following discounts will be applied: Team License {0}% discount, Business License {1}% discount, Enterprise License {2}% discount.", "WhatHappensWhenLicenseEndsExplanation7": "You can extend (renew) your license if you want to continue getting these benefits. If you extend your license within <strong>1 month</strong> after your license expires, the following discounts will be applied: Team License {0}% discount, Business License {1}% discount, Enterprise License {2}% discount.",
"WhatHappensWhenLicenseEndsExplanation8": "The ABP projects you generated are not stored on our servers. Therefore, it is your responsibility to keep the source code you download. When your license expires, there's no way to get your generated ABP project source code.",
"WhenShouldIRenewMyLicense": "When should I renew my license?", "WhenShouldIRenewMyLicense": "When should I renew my license?",
"WhenShouldIRenewMyLicenseExplanation": "If you renew your license within <strong>1 month</strong> after your license expires, the following discounts will be applied: Team License {0}% discount, Business License {1}% discount, Enterprise License {2}% discount. However, if you renew your license after <strong>1 month</strong> since the expiry date of your license, the renewal price will be the same as the license purchase price and there will be no discount on your renewal.", "WhenShouldIRenewMyLicenseExplanation": "If you renew your license within <strong>1 month</strong> after your license expires, the following discounts will be applied: Team License {0}% discount, Business License {1}% discount, Enterprise License {2}% discount. However, if you renew your license after <strong>1 month</strong> 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?", "TrialPlan": "Do you have a trial plan?",
@ -271,7 +272,7 @@
"Enterprise": "Enterprise", "Enterprise": "Enterprise",
"Custom": "Custom", "Custom": "Custom",
"IncludedDeveloperLicenses": "Included developer licenses", "IncludedDeveloperLicenses": "Included developer licenses",
"CustomLicenceOrAdditionalServices": "Need custom licence or additional services?", "CustomLicenceOrAdditionalServices": "Need custom license or additional services?",
"CustomOrVolumeLicense": "Custom or volume license", "CustomOrVolumeLicense": "Custom or volume license",
"LiveTrainingSupport": "Live training & support", "LiveTrainingSupport": "Live training & support",
"AndMore": "and more", "AndMore": "and more",
@ -506,6 +507,7 @@
"OnboardingTrainingFaqTitle": "Do you have ABP onboarding training?", "OnboardingTrainingFaqTitle": "Do you have ABP onboarding training?",
"OnboardingTrainingFaqExplanation": "Yes, we have ABP Training Services to help you get your ABP project started fast. You will learn about ABP from an ABP core team member and you will get the skills to begin your ABP project. In the onboarding training, we will explain how to set up your development environment, install the required tools, create a fully functional CRUD page. The training will be live and the Zoom application will be used, and we are open to using other online meeting platforms. The language of the training will be English. You can also ask your questions about ABP during the sessions. A convenient time and date will be planned for both parties. To get more information, contact us at <a href=\"mailto:info@abp.io\">info@abp.io</a>.", "OnboardingTrainingFaqExplanation": "Yes, we have ABP Training Services to help you get your ABP project started fast. You will learn about ABP from an ABP core team member and you will get the skills to begin your ABP project. In the onboarding training, we will explain how to set up your development environment, install the required tools, create a fully functional CRUD page. The training will be live and the Zoom application will be used, and we are open to using other online meeting platforms. The language of the training will be English. You can also ask your questions about ABP during the sessions. A convenient time and date will be planned for both parties. To get more information, contact us at <a href=\"mailto:info@abp.io\">info@abp.io</a>.",
"AddBasket": "Add to Basket", "AddBasket": "Add to Basket",
"SendTrainingRequest": "Send Training Request" "SendTrainingRequest": "Send Training Request",
"OnlyEnglishVersionOfThisDocumentIsTheRecentAndValid": "* The English version of this document is the most up-to-date and the English version will prevail in any dispute."
} }
} }

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en-GB.json

@ -102,6 +102,7 @@
"PostRequestMessageBody": "Here is the list of the requested posts by the Community. Do you want to write a requested post? Please click on the request and join the discussion.", "PostRequestMessageBody": "Here is the list of the requested posts by the Community. Do you want to write a requested post? Please click on the request and join the discussion.",
"Language": "Language", "Language": "Language",
"CreatePostLanguageInfo": "Language of the post", "CreatePostLanguageInfo": "Language of the post",
"SeeMore": "See More" "SeeMore": "See More",
"MemberNotPublishedPostYet": "This member hasn't published any posts yet."
} }
} }

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -165,6 +165,7 @@
"SortBy": "Sort by", "SortBy": "Sort by",
"NoPublishedEventsYet": "No published events yet.", "NoPublishedEventsYet": "No published events yet.",
"SubscribeYoutubeChannel": "Subscribe to the Youtube Channel", "SubscribeYoutubeChannel": "Subscribe to the Youtube Channel",
"Enum:EventType:0": "Talks" "Enum:EventType:0": "Talks",
"MemberNotPublishedPostYet": "This member hasn't published any posts yet."
} }
} }

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/tr.json

@ -142,6 +142,7 @@
"MinimumSearchContent": "En az 3 karakter girmelisiniz!", "MinimumSearchContent": "En az 3 karakter girmelisiniz!",
"Volo.AbpIo.Domain:060001": "Kaynak URL (\"{PostUrl}\") Github URL'si değil", "Volo.AbpIo.Domain:060001": "Kaynak URL (\"{PostUrl}\") Github URL'si değil",
"Volo.AbpIo.Domain:060002": "Makale İçeriği Github(\"{PostUrl}\") kaynağında mevcut değil.", "Volo.AbpIo.Domain:060002": "Makale İçeriği Github(\"{PostUrl}\") kaynağında mevcut değil.",
"Volo.AbpIo.Domain:060003": "Makale içeriği bulunamadı!" "Volo.AbpIo.Domain:060003": "Makale içeriği bulunamadı!",
"MemberNotPublishedPostYet": "Bu üye henüz bir gönderi yayınlamadı."
} }
} }

2
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -174,6 +174,7 @@
"CreateProjectWizard": "This wizard creates a new project from the startup template which is properly configured to jump start your project.", "CreateProjectWizard": "This wizard creates a new project from the startup template which is properly configured to jump start your project.",
"TieredOption": "Creates a tiered solution where Web and Http API layers are physically separated. If not checked, creates a layered solution which is less complex and suitable for most scenarios.", "TieredOption": "Creates a tiered solution where Web and Http API layers are physically separated. If not checked, creates a layered solution which is less complex and suitable for most scenarios.",
"SeparateIdentityServerOption": "Separates the server side into two applications: The first one is for the identity server and the second one is for your server side HTTP API.", "SeparateIdentityServerOption": "Separates the server side into two applications: The first one is for the identity server and the second one is for your server side HTTP API.",
"ProgressiveWebApplicationOption": "Specifies the project as Progressive Web Application",
"UseslatestPreVersion": "Uses the latest pre-release version", "UseslatestPreVersion": "Uses the latest pre-release version",
"ReadTheDocumentation": "Read The Documentation", "ReadTheDocumentation": "Read The Documentation",
"Documentation": "Documentation", "Documentation": "Documentation",
@ -296,6 +297,7 @@
"EnterYouEmailToGetNews": "Enter your email to get the latest news about the ABP Framework", "EnterYouEmailToGetNews": "Enter your email to get the latest news about the ABP Framework",
"Tiered": "Tiered", "Tiered": "Tiered",
"SeparateIdentityServer": "Separate Identity Server", "SeparateIdentityServer": "Separate Identity Server",
"ProgressiveWebApplication": "Progressive Web Application",
"Preview": "Preview", "Preview": "Preview",
"CreateANewSolution": "Create a new solution", "CreateANewSolution": "Create a new solution",
"ABPFrameworkFeatures": "ABP Framework <span class=\"gradient-framework\">Features</span>", "ABPFrameworkFeatures": "ABP Framework <span class=\"gradient-framework\">Features</span>",

2
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json

@ -174,6 +174,8 @@
"CreateProjectWizard": "Bu sihirbaz, projenize atlamak için uygun şekilde yapılandırılmış başlangıç şablonundan yeni bir proje oluşturur.", "CreateProjectWizard": "Bu sihirbaz, projenize atlamak için uygun şekilde yapılandırılmış başlangıç şablonundan yeni bir proje oluşturur.",
"TieredOption": "Web ve Http API katmanlarının fiziksel olarak ayrıldığı katmanlı bir çözüm oluşturur. İşaretlenmezse, daha az karmaşık olan ve çoğu senaryo için uygun olan katmanlı bir çözüm oluşturur.", "TieredOption": "Web ve Http API katmanlarının fiziksel olarak ayrıldığı katmanlı bir çözüm oluşturur. İşaretlenmezse, daha az karmaşık olan ve çoğu senaryo için uygun olan katmanlı bir çözüm oluşturur.",
"SeparateIdentityServerOption": "Sunucu tarafını iki uygulamaya ayırır: Birincisi kimlik sunucusu için, ikincisi ise sunucu tarafı HTTP API'niz içindir.", "SeparateIdentityServerOption": "Sunucu tarafını iki uygulamaya ayırır: Birincisi kimlik sunucusu için, ikincisi ise sunucu tarafı HTTP API'niz içindir.",
"ProgressiveWebApplicationOption": "Projeyi Progresif Web Uygulaması olarak ayarlayın.",
"ProgressiveWebApplication": "Progresif Web Uygulaması",
"UseslatestPreVersion": "En son yayın öncesi sürümünü kullanır", "UseslatestPreVersion": "En son yayın öncesi sürümünü kullanır",
"ReadTheDocumentation": "<span class=\"text-primary\">Belgeleri okuyun</span><span class=\"text-success\"></span>", "ReadTheDocumentation": "<span class=\"text-primary\">Belgeleri okuyun</span><span class=\"text-success\"></span>",
"Documentation": "belgeler", "Documentation": "belgeler",

1
build/common.ps1

@ -14,6 +14,7 @@ $solutionPaths = @(
"../modules/feature-management", "../modules/feature-management",
"../modules/identity", "../modules/identity",
"../modules/identityserver", "../modules/identityserver",
"../modules/openiddict",
"../modules/tenant-management", "../modules/tenant-management",
"../modules/audit-logging", "../modules/audit-logging",
"../modules/background-jobs", "../modules/background-jobs",

2
common.props

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>5.3.0</Version> <Version>5.4.0</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> <PackageProjectUrl>https://abp.io/</PackageProjectUrl>

17
docs/en/Background-Jobs.md

@ -155,8 +155,6 @@ public class MyModule : AbpModule
} }
```` ````
> Default background manager (see below) does not support multiple processes execute the same job queue. So, if you have multiple running instance of your application and you are using the default background job manager, you should only enable one application instance process the job queue.
## Default Background Job Manager ## Default Background Job Manager
ABP framework includes a simple `IBackgroundJobManager` implementation that; ABP framework includes a simple `IBackgroundJobManager` implementation that;
@ -189,12 +187,23 @@ public class MyModule : AbpModule
### Data Store ### Data Store
The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction. So, you can replace the implementation if you want. The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction to store the jobs.
Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). If you don't want to use this module, you should implement the `IBackgroundJobStore` interface yourself.
> Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice. > Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice.
### Clustered Deployment
The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time.
However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, **please follow the [distributed lock](Distributed-Locking.md) document to configure a provider for your application**, if it is not already configured.
If you don't want to use a distributed lock provider, you may go with the following options:
* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs).
* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance.
## Integrations ## Integrations
Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations. Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations.

7
docs/en/Background-Workers.md

@ -126,10 +126,11 @@ Background workers only work if your application is running. If you host the bac
Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example). Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example).
If that's a problem for your workers, you have two options; If that's a problem for your workers, you have the following options:
* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them. * Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed.
* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers. * Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers.
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance.
## Integrations ## Integrations

258
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/POST.md

@ -0,0 +1,258 @@
# ABP.IO Platform 5.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 **5.3 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
> **The planned release date for the [5.3.0 Stable](https://github.com/abpframework/abp/milestone/69) version is May 31, 2022**.
Please try this version and provide feedback for a more stable ABP version 5.3! Thank you all.
## Get Started with the 5.3 RC
Follow the steps below to try version 5.3.0 RC today:
1) **Upgrade** the ABP CLI to version `5.3.0-rc.1` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 5.3.0-rc.1
````
**or install** it if you haven't before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 5.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 *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**.
You can use any IDE that supports .NET 6.x, like **[Visual Studio 2022](https://visualstudio.microsoft.com/downloads/)**.
## Migration Notes
There is a change in this version that may effect your applications:
* Upgraded the [AutoMapper](https://github.com/AutoMapper/AutoMapper) library to **v11.0.1**. So, you need to change your project's target SDK that use the **AutoMapper** library (typically your `*.Application` project). You can change it from `netstandard2.0` to `netstandard2.1` or `net6` if needed. You can write to [#12189](https://github.com/abpframework/abp/pull/12189) if you need any help.
## What's New with ABP Framework 5.3?
In this section, I will introduce some major features released with this version. Here is a brief list of titles explained in the next sections:
* Single-layer option added to the [*Get Started*](https://abp.io/get-started) page
* PWA Support for Startup Templates
* Introduced the `Volo.Abp.Gdpr.Abstractions` package
* Batch Publish Events from Outbox to the Event Bus
* Improvements on **eShopOnAbp** Project & E-Book Announcement
* LeptonX Lite Documentations & Project Status & Roadmap
* OpenIddict Module & Keycloack Integration
* Deployment Documentations
* Other News
### Single-layer Option on *Get Started* Page
We've created a new startup template named `app-nolayers` and [announced](https://blog.abp.io/abp/ABP.IO-Platform-5-2-RC-Has-Been-Published) it in the previous version. In this version, we've also added this startup template option to the *Get Started* page.
*You can examine the screenshot below to see how to create an `app-nolayers` template from the ["Get Started"](https://abp.io/get-started) page:*
![](./app-nolayers-get-started.png)
### PWA Support for Startup Templates
ABP v5.3 application startup template now supports PWA for Blazor WASM & Angular UIs. To create a startup template with the PWA support, you can use the `--pwa` parameter.
Example:
```bash
abp new MyProgressiveWebApp -t app -u blazor --pwa
```
### Introducing the `Volo.Abp.Gdpr.Abstractions` Package
A new `Volo.Abp.Gdpr.Abstractions` package has been added to the framework. This is an abstraction package, so doesn't contain any actual GDPR implementation. It defines some classes and interfaces to put a standard for who want to implement a GDPR module that can run in a modular or microservice system.
At that point, we are introducing the **GDPR Module** for the ABP Commercial customers and this module does the GDPR-related operations on behalf of you, such as *"Download/Delete Personal Data"*. I'll describe the **GDPR Module** later in this blog post.
> Please see the **GDPR Module** section below to learn more about this module.
### Batch Publish Events from Outbox to the Event Bus
We introduced the "Transactional Outbox & Inbox Patterns" in [**ABP v5.0**](https://blog.abp.io/abp/ABP-IO-Platform-5.0-RC-1-Has-Been-Released), it was one of the most awaited features by several software developers.
We've made some optimizations for the **Batch Event Publishing** in this version, you can examine the related development from [here](https://github.com/abpframework/abp/pull/11243). After the optimization, the results are impressive. It is enabled by default (if you have configured [event outbox](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#outbox-inbox-for-transactional-events)), so you don't need to any manual configuration.
### Improvements on eShopOnAbp Project & E-Book Announcement
There are some developments on the [eShopOnAbp project](https://github.com/abpframework/eShopOnAbp) made in this version. You can see the brief descriptions of some of the improvements below:
* Local certificates have been created to use while working in Kubernetes and also Helm Charts have been updated. See [#107](https://github.com/abpframework/eShopOnAbp/pull/107).
* The Order Management page has been created. See [#92](https://github.com/abpframework/eShopOnAbp/pull/92).
* Database migration event handlers have been removed and "Distributed Locking" is now used for database migrations. See [#85](https://github.com/abpframework/eShopOnAbp/pull/85) and [#102](https://github.com/abpframework/eShopOnAbp/pull/102).
* Switched from Ocelot to YARP as the gateway. See [#97](https://github.com/abpframework/eShopOnAbp/pull/97).
We have exciting news to share with the community, we're working on an "ABP Microservice Development" e-book. In this book, we're using the eShopOnAbp project as a reference microservice solution and we're trying to explain our experiences during the microservice application development process through this project.
We're planning to create this book in nine chapters and make it available after the third chapter is written. After that, you will be able to download this free e-book from the [abp.io](https://abp.io/) website.
### LeptonX Lite Documentations & Project Status & Roadmap
It is finally here, we've released the **1.0.0-beta.1** version for the **LeptonX Lite**.
![](./leptonx-lite-documentations.png)
Lepton X Lite documents have been written for the three UI types within this version. You can see the related documentation from the screenshot above. You can follow these documents and try the new **LeptonX Lite Theme**.
We don't suggest using the **beta.1** version on production, we highly demand you to test **LeptonX Lite** and provide feedback to us. It's really important for us to be able to release a more stable version. Thanks in advance.
For the following versions (beta.2 and RC versions), we will focus on:
* Fixing the reported bugs from the community
* Providing documentations as much as possible
* Adding new custom pages to the demo
### OpenIddict Module & Keycloack Integration
We have [announced the plan of replacing the IdentityServer](https://github.com/abpframework/abp/issues/11989). ABP currently uses **IdentityServer4** to add **OAuth** features as built-in on the server-side. However, since *IdentityServer4's support ends at the end of the year 2022*. Its replacement is Duende IdentityServer, which is not a free software anymore. (see [more](https://blog.duendesoftware.com/posts/20220111_fair_trade/))
Therefore, we've decided to completely drop the **IdentityServer4** from the ABP platform and implement the [OpenIddict](https://github.com/openiddict/openiddict-core) and install onto the startup templates.
We've implemented both open source and commercial OpenIddict modules, we plan to remove Identity Server and replace it with OpenIddict for template projects in **ABP v5.4**. Please check [#12084](https://github.com/abpframework/abp/pull/12084) to see the development made on the open-source side.
We're creating the documentation for the OpenIddict Module, if you want to have general knowledge about this module, you can check the documentation from [here](https://github.com/abpframework/abp/blob/dev/docs/en/Modules/OpenIddict.md). Currently, this is a draft documentation but it gives overall knowledge about the OpenIddict Module, we'll complete this documentation in ABP v5.4 and you'll be able to read it completely.
Currently, we are also working on Keycloak integration possibilities in parallel to the OpenIddict integration research and we've prepared some samples that you can examine. You can see [#154](https://github.com/abpframework/abp-samples/pull/154) and [#158](https://github.com/abpframework/abp-samples/pull/158).
### Deployment Documentations
Deploying an ABP-based application is not so different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. However, we wanted to prepare a "Deployment Guide" to mention the important points and considerations.
![](./deployment-documentation.png)
In the [Deploying to a Clustered Environment](https://docs.abp.io/en/abp/5.3/Deployment/Clustered-Environment) documentation, we've documented the topics that you should consider when you are developing your application to a clustered environment and explained how you can deal with these topics in your ABP-based application.
### Other News
* Global Features were only accessible from the C# code. From this version and on, Global Features can be also provided from application configurations. See [#12043](https://github.com/abpframework/abp/pull/12043).
* Getting the user's detailed information (name, surname and phone number) from external login. See [#12085](https://github.com/abpframework/abp/pull/12085).
* Date Pipes for Angular. See [#11909](https://github.com/abpframework/abp/issues/11909).
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 closed with this version.
## What's New with ABP Commercial 5.3?
### GDPR Module
> **GDPR (General Data Protection Regulation)** is a regulation in EU law on data protection and known as the toughest privacy and security law in the world. GDPR applies to any organization operating within the EU, as well as any organizations outside of the EU which offer goods or services to customers or businesses in the EU.
With this version, we are introducing the new **GDPR Module**. This was one of the most awaited features, so we've prioritized it and implemented it in this version.
The GDPR Module is pre-installed in the [startup templates](https://docs.abp.io/en/commercial/5.3/startup-templates/index) for MVC. So, no need to manually install it. When you create a new startup template, you can directly use this module. We'll also implement this module for the other UI types as soon as possible and also add extra functionality such as "Cookie Consent" and more.
Currently, there are two main functions of this module and they are "Download Personal Data" and "Delete Personal Data".
![](./gdpr-user-menu.png)
There is a "Personal Data" section in the user menu as in the screenshot above and when you click on this section, you'll be redirected to the "Personal Data" page. On that page, you can either request to "Download Personal Data" or "Delete Personal Data".
![](./gdpr-personal-data-page.png)
After you've requested to download "Personal Data", you need to wait for 1 hour by default (you can configure the related option). Because the GDPR module is developed by considering the distributed systems and therefore a specific time should be passed to ensure all the published events are handled and all personal data is collected.
### CMS Kit Pro - Polling Feature
We've added a **Polling** feature to the **CMS Kit Pro** module. This feature allows you to use a questionnaire/voting system in your application easily. You can create a question, define some options for it and the poll will be created for you. You can see the example poll in the screenshot below:
![](./poll-question-example.png)
Also, there is an admin side of the Polling Feature. You can easily manage your polls in your admin (back-office) project. You can create, update, delete and show the results of the poll on the Polls page:
![](./poll-questions.png)
### OAuth Resource Owner Password as External Login Provider
> The Resource Owner Password flow allows for the exchanging of the username and password of a user for an access token. When using the resource owner password credentials grant, the user provides the credentials (username and password) directly to the application.
Now, you can login by entering a username and password from an OAuth server.
Example: Use OAuth external login provider with Keycloak:
![](./oauth-external-provider.gif)
### Suite New Features & Enhancements
In this version, there are some enhancements and new features in **Suite** and they are listed briefly below:
* It's now possible to create an **app-nolayers (Application - single layer)** template via Suite and also code-generation is supported for the **app-nolayers** template with this version.
* Suite now allows users to see and download its logs.
* Suite now allows generating code via CLI. If you have a JSON file that contains code blocks, like entity configurations, you can use the `abp suite generate` command to generate CRUD pages based on it.
Example:
```bash
abp suite generate -e C:\Users\.suite\entities\Country.json -s C:\Users\my-proj\SuiteProj\SuiteProj.sln
```
### Suite Webinar: Take a closer look at the code generation
![](./webinar.png)
We've organized a webinar for Suite and in this webinar, we've talked about ABP Suite's capabilities, important features and more...
You can watch the event from [here](https://www.youtube.com/watch?v=RFArBh60RSA&t=3s), if you haven't watched it yet.
### Docker Compose Configurations for Single Layer Startup Template
Dockerfiles, docker-compose files and build script files have been added to the Single Layer Startup Template (app-nolayers) with this version.
And this way, applications created with this template now can be deployed more easily.
### Microservice Solution Enhancements
There are some enhancements made in the Microservice Solution. You can see the list of these enhancements:
* Initial migration on the template has been updated with the small improvement that was made in the **Language Management** module.
* Database migration event handlers have been removed and "Distributed Locking" is now used for the database migrations.
### PWA Support for the Application Pro Template
Application Pro template also supports the PWA for Blazor WASM & Angular UIS. To create a startup template with the PWA support, you can use the `--pwa` parameter.
Example:
```bash
abp new MyProgressiveWebApp -t app-pro -u blazor --pwa
```
## Community News
### New ABP Community Posts
* [Anto Subash](https://twitter.com/antosubash) created a series named ["Microservice with ABP"](https://blog.antosubash.com/posts/abp-microservice-series) and shared a couple of video posts about the ABP Microservice solution.
* [Francisco Kadzi](https://github.com/CiscoNinja) has created his first ABP Community article that shows how to ["Customize ABP Angular Application UI with AdminLTE"](https://community.abp.io/posts/customize-abp-angular-application-ui-with-adminlte.-7qu1m67s).
* [Jack Fistelmann](https://github.com/nebula2) has created an article to introduce a helpful project extension to speed up development on Visual Studio. You can read the article [here](https://community.abp.io/posts/using-switch-startup-project-extension-for-visual-studio-52yyw27v).
* [Jack Fistelmann](https://github.com/nebula2) has also created an article to show how you can generate PDF files with the `Sycyber.Core` package in ABP-based applications. You can read it [here](https://community.abp.io/posts/generate-pdfs-in-an-abp-framework-project-using-scryber.core-x9yh1vfa).
* [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) has created an article to show ["Dealing with Multiple Implementations of a Service in ASP.NET Core & ABP Dependency Injection"](https://community.abp.io/posts/dealing-with-multiple-implementations-of-a-service-in-asp.net-core-abp-dependency-injection-ysfp4ho2) with examples.
* [Manoj Kumar](https://community.abp.io/members/manojkumar.t@shloklabs.com) submitted a new article about how to use "ABP authentication in a Flutter application". It was a frequently asked topic, which you can read [here](https://community.abp.io/posts/flutter-web-authentication-from-abp-mp6l2ehx).
* [Engincan Veske](https://twitter.com/EngincanVeske) created a new Community Article to show "Concurrency Check/Control in ABP". You can read it [here](https://community.abp.io/posts/handle-concurrency-with-ef-core-in-an-abp-framework-project-with-asp.net-core-mvc-jlkc3w8f).
### ABP Community Talks 2022.4: How can you contribute to the open source ABP Framework? (May 10, 2022 - 17:00 UTC)
![](./community-talks-2022.4.png)
We've [asked you to pick the topic of the next Community Talks](https://twitter.com/abpframework/status/1514567683072745474?s=20&t=rJfHrB3DYDNsk2EXS8zBBQ) and you've chosen the "How to contribute to open source ABP Framework?" for the next talk topic. So, in this Community Talk, we will be talking about "How to contribute to ABP Framework" with one of the top contributors of the ABP Framework, [Ismail Yılmaz](https://github.com/iyilm4z). The event will be on **May 10, 2022, at 17:00 (UTC)** on YouTube.
> You can register for the event from [here](https://kommunity.com/volosoft/events/abp-community-talks-20224-how-to-contribute-to-the-open-source-abp-framework-d9b50664), if you haven't registered yet.
You can also [subscribe to the Volosoft channel](https://www.youtube.com/channel/UCO3XKlpvq8CA5MQNVS6b3dQ) to be informed about future ABP events and videos.
### Discord Server
We've created an official ABP Discord server so the ABP Community can interact with each other and created a blog-post to introduce it. You can read the [ABP Discord Server announcement post](https://blog.abp.io/abp/Official-ABP-Discord-Server-is-Here) to learn more about the ABP Discord Server.
Thanks to the ABP Community, **700+** people joined our Discord Server so far and it grows every day.
You can join our Discord Server from [here](https://discord.gg/abp), if you haven't yet.

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/app-nolayers-get-started.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/community-talks-2022.4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/cover-53.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/deployment-documentation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-personal-data-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/gdpr-user-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/leptonx-lite-documentations.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/oauth-external-provider.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-new-question.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-question-example.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/poll-questions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
docs/en/Blog-Posts/2022-05-09 v5_3_Preview/webinar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

3
docs/en/CLI.md

@ -92,8 +92,10 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. * `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios.
* `angular`: Angular UI. There are some additional options for this template: * `angular`: Angular UI. There are some additional options for this template:
* `--separate-identity-server`: The Identity Server project comes as a separate project and runs at a different endpoint. It separates the Identity Server from the API Host application. If not specified, you will have a single endpoint in the server side. * `--separate-identity-server`: The Identity Server project comes as a separate project and runs at a different endpoint. It separates the Identity Server from the API Host application. If not specified, you will have a single endpoint in the server side.
* `--pwa`: Specifies the project as Progressive Web Application.
* `blazor`: Blazor UI. There are some additional options for this template: * `blazor`: Blazor UI. There are some additional options for this template:
* `--separate-identity-server`The Identity Server project comes as a separate project and runs at a different endpoint. It separates the Identity Server from the API Host application. If not specified, you will have a single endpoint in the server side. * `--separate-identity-server`The Identity Server project comes as a separate project and runs at a different endpoint. It separates the Identity Server from the API Host application. If not specified, you will have a single endpoint in the server side.
* `--pwa`: Specifies the project as Progressive Web Application.
* `blazor-server`: Blazor Server UI. There are some additional options for this template: * `blazor-server`: Blazor Server UI. There are some additional options for this template:
* `--tiered`: The Identity Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *IdentityServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. * `--tiered`: The Identity Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *IdentityServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project.
* `none`: Without UI. No front-end layer will be created. There are some additional options for this template: * `none`: Without UI. No front-end layer will be created. There are some additional options for this template:
@ -115,7 +117,6 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample
* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers:
* `ef`: Entity Framework Core. * `ef`: Entity Framework Core.
* `mongodb`: MongoDB. * `mongodb`: MongoDB.
* `--pwa`: Specifies to Configure Angular or Blazor WebAssembly project as Progressive Web Application.
* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory.
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--preview`: Use latest preview version. * `--preview`: Use latest preview version.

6
docs/en/Caching.md

@ -1,7 +1,9 @@
# Caching # Distributed Caching
ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed).
> **Default implementation of the `IDistributedCache` interface is` MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use Redis as the distributed cache server.
## Installation ## Installation
> This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually. > This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually.
@ -27,8 +29,6 @@ ASP.NET Core defines the `IDistributedCache` interface to get/set the cache valu
> `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**. > `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**.
> Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server.
See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information. See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information.
### `IDistributedCache<TCacheItem>` Interface ### `IDistributedCache<TCacheItem>` Interface

54
docs/en/Contribution/How-to-Contribute-abp.io-as-a-frontend-developer.md

@ -0,0 +1,54 @@
# How to contribute to abp.io as a frontend developer
## How to setup development environment
### Pre-requirements
- Dotnet core SDK https://dotnet.microsoft.com/en-us/download
- Nodejs LTS https://nodejs.org/en/
- Docker https://docs.docker.com/engine/install
- Angular CLI. https://angular.io/guide/what-is-angular#angular-cli
- Abp CLI https://docs.abp.io/en/abp/latest/cli
- A code editor
Note: This arcticle prepare Windows OS. You may change the path type of your OS. an Example
Windows: `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator\appsettings.json`
Unix: `templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json`
### Sample docker commands
You need to install SQL Server and Redis. You can install these programs without docker, but my example uses docker containers. Your computer should have Docker Engine. Then open the terminal en execute the commands one by one.
For the Sql Server
docker run -v sqlvolume:/var/opt/mssql -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=yourpassword -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-CU3-ubuntu-18.04
For the Redis
docker run -p 6379:6379 -d redis
Then we are ready to download and execute the code.
## Folder Structure
The app has a backend written in .net core (c#) and an angular app. It would help if you ran both of them.
### Running Backend App
The path of the Backend app is “templates\app\aspnet-core.” If you want to work with dockerized SQL Server, you should change connection strings for running with docker. The path of the connection string is
`templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator\appsettings.json`.
Before running the backend, you should run the Db migrator project. The DbMigrator created initial tables and values. The path of DbMigrator is `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.DbMigrator`. Open a terminal in the path and execute the command `dotnet run` in terminal
One last step before the running the backend is installing client-side libraries. Go to `templates\app\aspnet-core`. Open a terminal in the path and execute the command `abp install-libs` in terminal
Next step you should go to path of backend host project. The path is `templates\app\aspnet-core\src\MyCompanyName.MyProjectName.HttpApi.HostWithIds`. Open a terminal in the path and execute the command `dotnet run` in terminal
Your backend should be running successfully
### Running Frontend App
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`

4
docs/en/Contribution/Index.md

@ -70,3 +70,7 @@ If you want to make a change on a specific resource file, you can find the file
## Bug Report ## Bug Report
If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new). If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new).
## Setup Frontend Development Environment
[How to contribute to abp.io as a frontend developer](How-to-Contribute-abp.io-as-a-frontend-developer.md)

2
docs/en/Dependency-Injection.md

@ -131,7 +131,7 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie
If you do not specify which services to expose, ABP expose services by convention. So taking the ``TaxCalculator`` defined above: If you do not specify which services to expose, ABP expose services by convention. So taking the ``TaxCalculator`` defined above:
* The class itself is exposed by default. That means you can inject it by ``TaxCalculator`` class. * The class itself is exposed by default. That means you can inject it by ``TaxCalculator`` class.
* Default interfaces are exposed by default. Default interfaces are determined by naming convention. In this example, ``ICalculator`` and ``ITaxCalculator`` are default interfaces of ``TaxCalculator``, but ``ICanCalculate`` is not. * Default interfaces are exposed by default. Default interfaces are determined by naming convention. In this example, ``ICalculator`` and ``ITaxCalculator`` are default interfaces of ``TaxCalculator``, but ``ICanCalculate`` is not. A generic interface (e.g. `ICalculator<string>`) is also considered as a default interface if the naming convention is satisfied.
### Combining All Together ### Combining All Together

99
docs/en/Deployment/Clustered-Environment.md

@ -0,0 +1,99 @@
# Deploying to a Clustered Environment
This document introduces the topics that you should consider when you are deploying your application to a clustered environment where **multiple instances of your application run concurrently**, and explains how you can deal with these topics in your ABP based application.
> This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of an executable process.
>
> For example, if you are deploying your application to Kubernetes and configure your application or service to run in multiple pods, then your application or service runs in a clustered environment.
## Understanding the Clustered Environment
> You can skip this section if you are already familiar with clustered deployment and load balancers.
### Single Instance Deployment
Consider an application deployed as a **single instance**, as illustrated in the following figure:
![deployment-single-instance](../images/deployment-single-instance.png)
Browsers and other client applications can directly make HTTP requests to your application. You can put a web server (e.g. IIS or NGINX) between the clients and your application, but you still have a single application instance running in a single server or container. Single-instance configuration is **limited to scale** since it runs in a single server and you are limited with the server's capacity.
### Clustered Deployment
**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve different requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer**:
![deployment-clustered](../images/deployment-clustered.png)
### Load Balancers
[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forward an incoming HTTP request** to an instance of your application and return your response back to the client application.
Load balancers can use different algorithms for selecting the application instance while determining the application instance that is used to deliver the incoming request. **Round Robin** is one of the simplest and most used algorithms. Requests are delivered to the application instances in rotation. First instance gets the first request, second instance gets the second, and so on. It returns to the first instance after all the instances are used, and the algorithm goes like that for the next requests.
### Potential Problems
Once multiple instances of your application run in parallel, you should carefully consider the following topics:
* Any **state (data) stored in memory** of your application will become a problem when you have multiple instances. A state stored in memory of an application instance may not be available in the next request since the next request will be handled by a different application instance. While there are some solutions (like sticky sessions) to overcome this problem user-basis, it is a **best practice to design your application as stateless** if you want to run it in a cluster, container or/and cloud.
* **In-memory caching** is a kind of in-memory state and should not be used in a clustered application. You should use **distributed caching** instead.
* You shouldn't store data in the **local file system**. It should be available to all instances of your application. Different application instance may run in different containers or servers and they may not be able to have access to the same file system. You can use a **cloud or external storage provider** as a solution.
* If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work concurrently. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resources.
You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with the clustered deployment scenario. The following sections explain what you should do when you are deploying your ABP based application to a clustered environment.
## Switching to a Distributed Cache
ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and is only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem).
[ABP's Distributed Cache](../Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment.
> You should configure the cache provider for clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because the ABP Framework and the pre-built [application modules](../Modules/Index.md) are using distributed cache.
ASP.NET Core provides multiple integrations to use as your distributed cache provider, like [Redis](https://redis.io/) and [NCache](https://www.alachisoft.com/ncache/). You can follow [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to learn how to use them in your applications.
If you decided to use Redis as your distributed cache provider, **follow [ABP's Redis Cache Integration document](../Redis-Cache.md)** for the steps you need to follow to install it into your application and setup your Redis configuration.
> Based on your preferences while creating a new ABP solution, Redis cache might be pre-installed in your solution. For example, if you have selected the *Tiered* option with the MVC UI, Redis cache comes as pre-installed. Because, in this case, you have two applications in your solution and they should use the same cache source to be consistent.
## Using a Proper BLOB Storage Provider
If you have used ABP's [BLOB Storing](../Blob-Storing.md) feature with the [File System provider](../Blob-Storing-File-System.md), you should use another provider in your clustered environment since the File System provider uses the application's local file system.
The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since it uses your application's main database (or another database if you configure) to store BLOBs. However, you should remember that BLOBs are large objects and may quickly increase your database's size.
> [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database.
Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOG storage providers.
## Configuring Background Jobs
ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in the background. Background job queue is persistent and a queued task is guaranteed to be executed (it is re-tried if it fails).
ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment.
If you don't want to use a distributed lock provider, you may go with the following options:
* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs).
* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance.
> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer to your provider's documentation to learn how it should be configured for a clustered environment.
## Configuring a Distributed Lock Provider
ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. The ABP Framework and some pre-built [application modules](../Modules/Index.md) are using distributed locking for several reasons.
However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured.
## Implementing Background Workers
ASP.NET Core provides [hosted services](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) and ABP provides [background workers](../Background-Workers.md) to perform tasks in background threads in your application.
If your application has tasks running in the background, you should consider how they will behave in a clustered environment, especially if your background tasks are using the same resources. You should design your background tasks so that they continue to work properly in the clustered environment.
Assume that your background worker in your SaaS application checks user subscriptions and sends emails if their subscription renewal date approaches. If the background task runs in multiple application instances, it is probable to send the same email many times to some users, which will disturb them.
We suggest you to use one of the following approaches to overcome the problem:
* Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed.
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers.
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance.

9
docs/en/Deployment/Index.md

@ -0,0 +1,9 @@
# Deployment
Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. ABP's documentation doesn't contain much information on deployment. You can refer to your provider's documentation.
However, there are some topics that you should care about when you are deploying your applications. Most of them are general software deployment considerations, but you should understand how to handle them within your ABP based applications. We've prepared guides for this purpose and we suggest you to read these guides carefully before designing your deployment configuration.
## Guides
* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently.

219
docs/en/Distributed-Event-Bus.md

@ -20,7 +20,7 @@ For example, [pre-built application modules](Modules/Index.md) is designed to wo
There are two ways of publishing distributed events explained in the following sections. There are two ways of publishing distributed events explained in the following sections.
### IDistributedEventBus ### Using IDistributedEventBus to Publish Events
`IDistributedEventBus` can be [injected](Dependency-Injection.md) and used to publish a distributed event. `IDistributedEventBus` can be [injected](Dependency-Injection.md) and used to publish a distributed event.
@ -57,7 +57,7 @@ namespace AbpDemo
} }
```` ````
`PublishAsync` method gets a single parameter: the event object, which is responsible to hold the data related to the event. It is a simple plain class: `PublishAsync` method gets the event object, which is responsible to hold the data related to the event. It is a simple plain class:
````csharp ````csharp
using System; using System;
@ -80,15 +80,15 @@ Even if you don't need to transfer any data, you need to create a class (which i
#### Event Name #### Event Name
`EventName` attribute is optional, but suggested. If you don't declare it, the event name will be the full name of the event class, `AbpDemo.StockCountChangedEto` in this case. `EventName` attribute is optional, but suggested. If you don't declare it for an event type (ETO class), the event name will be the full name of the event class, `AbpDemo.StockCountChangedEto` in this case.
#### About Serialization for the Event Objects #### About Serialization for the Event Objects
Event transfer objects **must be serializable** since they will be serialized/deserialized to JSON or other format when it is transferred to out of the process. Event transfer objects (ETOs) **must be serializable** since they will be serialized/deserialized to JSON or other format when it is transferred to out of the process.
Avoid circular references, polymorphism, private setters and provide default (empty) constructors if you have any other constructor as a good practice (while some serializers may tolerate it), just like the DTOs. Avoid circular references, polymorphism, private setters and provide default (empty) constructors if you have any other constructor as a good practice (while some serializers may tolerate it), just like the DTOs.
### Inside Entity / Aggregate Root Classes ### Publishing Events Inside Entity / Aggregate Root Classes
[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish distributed events inside entity / aggregate root classes. [Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish distributed events inside entity / aggregate root classes.
@ -294,4 +294,211 @@ This example;
* Adds a selector to allow to publish the create, update and delete events for the `Product` entity. * Adds a selector to allow to publish the create, update and delete events for the `Product` entity.
* Configure to use the `ProductEto` as the event transfer object to publish for the `Product` related events. * Configure to use the `ProductEto` as the event transfer object to publish for the `Product` related events.
> Distributed event system use the [object to object mapping](Object-To-Object-Mapping.md) system to map `Product` objects to `ProductEto` objects. So, you need to configure the object mapping (`Product` -> `ProductEto`) too. You can check the [object to object mapping document](Object-To-Object-Mapping.md) to learn how to do it. > Distributed event system use the [object to object mapping](Object-To-Object-Mapping.md) system to map `Product` objects to `ProductEto` objects. So, you need to configure the object mapping (`Product` -> `ProductEto`) too. You can check the [object to object mapping document](Object-To-Object-Mapping.md) to learn how to do it.
## Transaction and Exception Handling
Distributed event bus works in-process (since default implementation is `LocalDistributedEventBus`) unless you configure an actual provider (e.g. [Kafka](Distributed-Event-Bus-Kafka-Integration.md) or [Redis](Distributed-Event-Bus-RabbitMQ-Integration.md)). In-process event bus always executes event handlers in the same [unit of work](Unit-Of-Work.md) scope that you publishes the events in. That means, if an event handler throws an exception, then the related unit of work (the database transaction) is rolled back. In this way, your application logic and event handling logic becomes transactional (atomic) and consistent. If you want to ignore errors in an event handler, you must use a `try-catch` block in your handler and shouldn't re-throw the exception.
When you switch to an actual distributed event bus provider (e.g. [Kafka](Distributed-Event-Bus-Kafka-Integration.md) or [Redis](Distributed-Event-Bus-RabbitMQ-Integration.md)), then the event handlers will be executed in different processes/applications as their purpose is to create distributed systems. In this case, the only way to implement transactional event publishing is to use the outbox/inbox patterns as explained in the *Outbox / Inbox for Transactional Events* section.
If you don't configure outbox/inbox pattern or use the `LocalDistributedEventBus`, then events are published at the end of the unit of work by default, just before the unit of work is completed (that means throwing exception in an event handler still rollbacks the unit of work), even if you publish them in the middle of unit of work. If you want to immediately publish the event, you can set `onUnitOfWorkComplete` to `false` while using `IDistributedEventBus.PublishAsync` method.
> Keeping the default behavior is recommended unless you don't have a unique requirement. `onUnitOfWorkComplete` option is not available when you publish events inside entity / aggregate root classes (see the *Publishing Events Inside Entity / Aggregate Root Classes* section).
## Outbox / Inbox for Transactional Events
The **[transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html)** is used to publishing distributed events within the **same transaction** that manipulates the application's database. When you enable outbox, distributed events are saved into the database inside the same transaction with your data changes, then sent to the actual message broker by a separate [background worker](Background-Workers.md) with a re-try system. In this way, it ensures the consistency between your database state and the published events.
The **transactional inbox pattern**, on the other hand, saves incoming events into database first. Then (in a [background worker](Background-Workers.md)) executes the event handler in a transactional manner and removes the event from the inbox queue in the same transaction. It ensures that the event is only executed one time by keeping the processed messages for a while and discarding the duplicate events received from the message broker.
Enabling the event outbox and inbox systems require a few manual steps for your application. Please apply the instructions in the following sections to make them running.
> Outbox and Inbox can be separately enabled and configured, so you may only use one of them if you want.
### Pre-requirements
* The outbox/inbox system uses the distributed lock system to handle concurrency when you run multiple instances of your application/service. So, you should **configure the distributed lock system** with one of the providers as [explained in this document](Distributed-Locking.md).
* The outbox/inbox system supports [Entity Framework Core](Entity-Framework-Core.md) (EF Core) and [MongoDB](MongoDB.md) **database providers** out of the box. So, your applications should use one of these database providers. For other database providers, see the *Implementing a Custom Database Provider* section.
> If you are using MongoDB, be sure that you enabled multi-document database transactions that was introduced in MongoDB version 4.0. See the *Transactions* section of the [MongoDB](MongoDB.md) document.
### Enabling event outbox
Open your `DbContext` class (EF Core or MongoDB), implement the `IHasEventOutbox` interface. You should end up by adding a `DbSet` property into your `DbContext` class:
```csharp
public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }
```
Add the following lines inside the `OnModelCreating` method of your `DbContext` class (only for EF Core):
```csharp
builder.ConfigureEventOutbox();
```
For EF Core, use the standard `Add-Migration` and `Update-Database` commands to apply changes into your database (you can skip this step for MongoDB). If you want to use the command-line terminal, run the following commands in the root directory of the database integration project:
```bash
dotnet ef migrations add "Added_Event_Outbox"
dotnet ef database update
```
Finally, write the following configuration code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md) (replace `YourDbContext` with your own `DbContext` class):
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
config.UseDbContext<YourDbContext>();
});
});
````
### Enabling event inbox
Open your `DbContext` class (EF Core or MongoDB), implement the `IHasEventInbox` interface. You should end up by adding a `DbSet` property into your `DbContext` class:
```csharp
public DbSet<IncomingEventRecord> IncomingEvents { get; set; }
```
Add the following lines inside the `OnModelCreating` method of your `DbContext` class (only for EF Core):
```csharp
builder.ConfigureEventInbox();
```
For EF Core, use the standard `Add-Migration` and `Update-Database` commands to apply changes into your database (you can skip this step for MongoDB). If you want to use the command-line terminal, run the following commands in the root directory of the database integration project:
```bash
dotnet ef migrations add "Added_Event_Inbox"
dotnet ef database update
```
Finally, write the following configuration code inside the `ConfigureServices` method of your [module class](Module-Development-Basics.md) (replace `YourDbContext` with your own `DbContext` class):
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure(config =>
{
config.UseDbContext<YourDbContext>();
});
});
````
### Additional Configuration
> The default configuration will be enough for most cases. However, there are some options you may want to set for outbox and inbox.
#### Outbox configuration
Remember how outboxes are configured:
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
// TODO: Set options
});
});
````
Here, the following properties are available on the `config` object:
* `IsSendingEnabled` (default: `true`): You can set to `false` to disable sending outbox events to the actual event bus. If you disable this, events can still be added to outbox, but not sent. This can be helpful if you have multiple applications (or application instances) writing to outbox, but use one of them to send the events.
* `Selector`: A predicate to filter the event (ETO) types to be used for this configuration. Should return `true` to select the event. It selects all the events by default. This is especially useful if you want to ignore some ETO types from the outbox, or want to define named outbox configurations and group events within these configurations. See the *Named Configurations* section.
* `ImplementationType`: Type of the class that implements the database operations for the outbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages.
#### Inbox configuration
Remember how inboxes are configured:
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Inboxes.Configure(config =>
{
// TODO: Set options
});
});
````
Here, the following properties are available on the `config` object:
* `IsProcessingEnabled` (default: `true`): You can set to `false` to disable processing (handling) events in the inbox. If you disable this, events can still be received, but not executed. This can be helpful if you have multiple applications (or application instances), but use one of them to execute the event handlers.
* `EventSelector`: A predicate to filter the event (ETO) types to be used for this configuration. This is especially useful if you want to ignore some ETO types from the inbox, or want to define named inbox configurations and group events within these configurations. See the *Named Configurations* section.
* `HandlerSelector`: A predicate to filter the event handled types (classes implementing the `IDistributedEventHandler<TEvent>` interface) to be used for this configuration. This is especially useful if you want to ignore some event handler types from inbox processing, or want to define named inbox configurations and group event handlers within these configurations. See the *Named Configurations* section.
* `ImplementationType`: Type of the class that implements the database operations for the inbox. This is normally set when you call `UseDbContext` as shown before. See *Implementing a Custom Outbox/Inbox Database Provider* section for advanced usages.
### Skipping Outbox
`IDistributedEventBus.PublishAsync` method provides an optional parameter, `useOutbox`, which is set to `true` by default. If you bypass outbox and immediately publish an event, you can set it to `false` for a specific event publishing operation.
### Advanced Topics
#### Named Configurations
> All the concepts explained in this section is also valid for inbox configurations. We will show examples only for outbox to keep the document shorter.
See the following outbox configuration code:
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
//TODO
});
});
````
This is equivalent of the following code:
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure("Default", config =>
{
//TODO
});
});
````
`Default` is this code indicates the configuration name. If you don't specify it (like in the previous code block), `Default` is used as the configuration name.
That means you can define more than one configuration for outbox (also for inbox) with different names. ABP runs all the configured outboxes.
Multiple outboxes can be needed if your application have more than one database and you want to run different outbox queues for different databases. In this case, you can use the `Selector` option to decide the events should be handled by an outbox. See the *Additional Configurations* section above.
#### Implementing a Custom Outbox/Inbox Database Provider
If your application or service is using a database provider other than [EF Core](Entity-Framework-Core.md) and [MongoDB](MongoDB.md), you should manually integrate outbox/inbox system with your database provider.
> Outbox and Inbox table/data must be stored in the same database with your application's data (since we want to create a single database transaction that includes application's database operations and outbox/inbox table operations). Otherwise, you should care about distributed (multi-database) transaction support which is not provided by most of the vendors and may require additional configuration.
ABP provides `IEventOutbox` and `IEventInbox` abstractions as extension point for the outbox/inbox system. You can create classes by implementing these interfaces and register them to [dependency injection](Dependency-Injection.md).
Once you implement your custom event boxes, you can configure `AbpDistributedEventBusOptions` to use your event box classes:
````csharp
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
config.ImplementationType = typeof(MyOutbox); //Your Outbox class
});
options.Inboxes.Configure(config =>
{
config.ImplementationType = typeof(MyInbox); //Your Inbox class
});
});
````
## See Also
* [Local Event Bus](Local-Event-Bus.md)

14
docs/en/Distributed-Locking.md

@ -1,7 +1,5 @@
# Distributed Locking # Distributed Locking
Distributed locking is a technique to manage many applications that try to access the same resource. Distributed locking is a technique to manage many applications that try to access the same resource. The main purpose is to allow only one of many applications to access the same resource at the same time. Otherwise, accessing the same object from various applications may corrupt the value of the resources.
The main purpose is to allow only one of many applications to access the same resource at the same time.
Otherwise, accessing the same object from various applications may corrupt the value of resources.
> ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library. > ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library.
@ -15,7 +13,7 @@ abp add-package Volo.Abp.DistributedLocking
This package provides the necessary API to use the distributed locking system, however, you should configure a provider before using it. This package provides the necessary API to use the distributed locking system, however, you should configure a provider before using it.
### Configuring a provider ### Configuring a Provider
The [DistributedLock](https://github.com/madelson/DistributedLock) library provides [various of implementations](https://github.com/madelson/DistributedLock#implementations) for the locking, like [Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md) and [ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md). The [DistributedLock](https://github.com/madelson/DistributedLock) library provides [various of implementations](https://github.com/madelson/DistributedLock#implementations) for the locking, like [Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md) and [ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md).
@ -61,7 +59,7 @@ This code gets the Redis connection string from the [configuration](Configuratio
There are two ways to use the distributed locking API: ABP's `IAbpDistributedLock` abstraction and [DistributedLock](https://github.com/madelson/DistributedLock) library's API. There are two ways to use the distributed locking API: ABP's `IAbpDistributedLock` abstraction and [DistributedLock](https://github.com/madelson/DistributedLock) library's API.
### Using the IAbpDistributedLock service ### Using the IAbpDistributedLock Service
`IAbpDistributedLock` is a simple service provided by the ABP framework for simple usage of distributed locking. `IAbpDistributedLock` is a simple service provided by the ABP framework for simple usage of distributed locking.
@ -103,6 +101,10 @@ namespace AbpDemo
* `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application. * `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application.
* `cancellationToken`: A cancellation token that can be triggered later to cancel the operation. * `cancellationToken`: A cancellation token that can be triggered later to cancel the operation.
### Using DistributedLock library's API ### Using DistributedLock Library's API
ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details. ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details.
## The Volo.Abp.DistributedLocking.Abstractions Package
If you are building a reusable library or an application module, then you may not want to bring an additional dependency to your module for simple applications that run as a single instance. In this case, your library can depend on the [Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions) package which defines the `IAbpDistributedLock` service and implements it as in-process (not distributed actually). In this way, your library can run properly (without a distributed lock provider dependency) in an application that runs as a single instance. If the application is deployed to a [clustered environment](Deployment/Clustered-Environment.md), then the application developer should install a real distributed provider as explained in the *Installation* section.

4
docs/en/Getting-Started-Running-Solution.md

@ -91,10 +91,10 @@ Right click to the `.DbMigrator` project and select **Set as StartUp Project**
## Run the Application ## Run the Application
> Please execute the `abp install-libs` command to restore the libs required by the web project before running the application.
{{ if UI == "MVC" || UI == "BlazorServer" }} {{ if UI == "MVC" || UI == "BlazorServer" }}
> Before starting the application, run `abp install-libs` command in your Web directory to restore the client-side libraries. This will populate the `libs` folder.
{{ if Tiered == "Yes" }} {{ if Tiered == "Yes" }}
> Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below. > Tiered solutions use **Redis** as the distributed cache. Ensure that it is installed and running in your local computer. If you are using a remote Redis Server, set the configuration in the `appsettings.json` files of the projects below.

44
docs/en/Local-Event-Bus.md

@ -6,7 +6,7 @@ The Local Event Bus allows services to publish and subscribe to **in-process eve
There are two ways of publishing local events explained in the following sections. There are two ways of publishing local events explained in the following sections.
### ILocalEventBus ### Publishing Events Using the ILocalEventBus
`ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event. `ILocalEventBus` can be [injected](Dependency-Injection.md) and used to publish a local event.
@ -64,7 +64,7 @@ namespace AbpDemo
Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case). Even if you don't need to transfer any data, you need to create a class (which is an empty class in this case).
### Inside Entity / Aggregate Root Classes ### Publishing Events Inside Entity / Aggregate Root Classes
[Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish local events inside entity / aggregate root classes. [Entities](Entities.md) can not inject services via dependency injection, but it is very common to publish local events inside entity / aggregate root classes.
@ -109,7 +109,7 @@ namespace AbpDemo
`AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created, updated or deleted) into the database. `AggregateRoot` class defines the `AddLocalEvent` to add a new local event, that is published when the aggregate root object is saved (created, updated or deleted) into the database.
> If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event. > Tip: If an entity publishes such an event, it is a good practice to change the related properties in a controlled manner, just like the example above - `StockCount` can only be changed by the `ChangeStockCount` method which guarantees publishing the event.
#### IGeneratesDomainEvents Interface #### IGeneratesDomainEvents Interface
@ -149,10 +149,10 @@ namespace AbpDemo
} }
```` ````
That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic here. That's all. `MyHandler` is **automatically discovered** by the ABP Framework and `HandleEventAsync` is called whenever a `StockCountChangedEvent` occurs. You can inject any service and perform any required logic in your handler class.
* **Zero or more handlers** can subscribe to the same event. * **One or more handlers** can subscribe to the same event.
* A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler<TEvent>` interface for each event type. * A single event handler class can **subscribe to multiple events** by implementing the `ILocalEventHandler<TEvent>` interface for each event type.
If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope.
@ -160,10 +160,11 @@ If you perform **database operations** and use the [repositories](Repositories.m
### Transaction & Exception Behavior ### Transaction & Exception Behavior
When an event published, subscribed event handlers are immediately executed. So; Event handlers are always executed in the same [unit of work](Unit-Of-Work.md) scope, that means in the same database transaction with the code that published the event. If an event handler throws an exception, the unit of work (database transaction) is rolled back. So, **use try-catch yourself** in the event handler if you want to hide the error.
* If a handler **throws an exception**, it effects the code that published the event. That means it gets the exception on the `PublishAsync` call. So, **use try-catch yourself** in the event handler if you want to hide the error. When you call `ILocalEventBus.PublishAsync`, the event handlers are not immediately executed. Instead, they are executed just before the current unit of work completed (an unhandled exception in the handler still rollbacks the current unit of work). If you want to immediately execute the handlers, set the optional `onUnitOfWorkComplete` parameter to `false`.
* If the event publishing code is being executed inside a [Unit Of Work](Unit-Of-Work.md) scope, the event handlers also covered by the unit of work. That means if your UOW is transactional and a handler throws an exception, the transaction is rolled back.
> Keeping the default behavior is recommended unless you don't have a unique requirement. `onUnitOfWorkComplete` option is not available when you publish events inside entity / aggregate root classes (see the *Publishing Events Inside Entity / Aggregate Root Classes* section).
## Pre-Built Events ## Pre-Built Events
@ -195,35 +196,22 @@ namespace AbpDemo
} }
```` ````
This class subscribes to the `EntityCreatedEventData<IdentityUser>`, which is published just after a user was created. You may want to send a "Welcome" email to the new user. This class subscribes to the `EntityCreatedEventData<IdentityUser>`, which is published just after a user was created (but before the current transaction is completed). For example, you may want to send a "Welcome" email to the new user.
There are two types of these events: events with past tense and events with continuous tense.
### Events with Past Tense
Events with past tense are published when the related unit of work completed and the entity change successfully saved to the database. If you throw an exception on these event handlers, it **can not rollback** the transaction since it was already committed. The pre-built event types are;
The event types are;
* `EntityCreatedEventData<T>` is published just after an entity was successfully created. * `EntityCreatedEventData<T>` is published just after an entity was successfully created.
* `EntityUpdatedEventData<T>` is published just after an entity was successfully updated. * `EntityUpdatedEventData<T>` is published just after an entity was successfully updated.
* `EntityDeletedEventData<T>` is published just after an entity was successfully deleted. * `EntityDeletedEventData<T>` is published just after an entity was successfully deleted.
* `EntityChangedEventData<T>` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events. * `EntityChangedEventData<T>` is published just after an entity was successfully created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events.
### Events with Continuous Tense
Events with continuous tense are published before completing the transaction (if database transaction is supported by the database provider being used). If you throw an exception on these event handlers, it **can rollback** the transaction since it is not completed yet and the change is not saved to the database.
The event types are;
* `EntityCreatingEventData<T>` is published just before saving a new entity to the database.
* `EntityUpdatingEventData<T>` is published just before an existing entity is being updated.
* `EntityDeletingEventData<T>` is published just before an entity is being deleted.
* `EntityChangingEventData<T>` is published just before an entity is being created, updated or deleted. It can be a shortcut if you need to listen any type of change - instead of subscribing to the individual events.
#### How It Was Implemented? #### How It Was Implemented?
Pre-build events are published when you save changes to the database; Pre-build events are published when you save changes to the database;
* For EF Core, they are published on `DbContext.SaveChanges`. * For EF Core, they are published on `DbContext.SaveChanges`.
* For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system). * For MongoDB, they are published when you call repository's `InsertAsync`, `UpdateAsync` or `DeleteAsync` methods (since MongoDB has not a change tracking system).
## See Also
* [Distributed Event Bus](Distributed-Event-Bus.md)

4
docs/en/Migration-Guides/Abp-5-0-Blazor.md

@ -1,6 +1,6 @@
# ABP Blazor UI v4.x to v5.0 Migration Guide # ABP Blazor UI v4.x to v5.0 Migration Guide
> This document is for the ABP MVC / Razor Pages UI. See also [the main migration guide](Abp-5_0.md). > This document is for the Blazor UI. See also [the main migration guide](Abp-5_0.md).
## Upgrading to the latest Blazorise ## Upgrading to the latest Blazorise
@ -16,4 +16,4 @@ Replace `AddBootstrapProviders()` with `AddBootstrap5Providers()`.
## Update Bundle ## Update Bundle
Use `abp bundle` command to update bundles if you are using Blazor WebAssembly. Use `abp bundle` command to update bundles if you are using Blazor WebAssembly.

2
docs/en/Migration-Guides/Abp-5_0-Angular.md

@ -1,6 +1,6 @@
# Angular UI v4.x to v5.0 Migration Guide # Angular UI v4.x to v5.0 Migration Guide
This document is for the ABP MVC / Razor Pages UI. See also [the main migration guide](Abp-5_0.md). > This document is for the Angular UI. See also [the main migration guide](Abp-5_0.md).
## Overall ## Overall

11
docs/en/Migration-Guides/Abp-5_3.md

@ -0,0 +1,11 @@
# ABP Version 5.3 Migration Guide
This document is a guide for upgrading ABP v5.2 solutions to ABP v5.3. There is a change in this version that may effect your applications, please read it carefully and apply the necessary changes to your application.
## AutoMapper Upgraded to v11.0.1
AutoMapper library upgraded to **v11.0.1** in this version. So, you need to change your project's target SDK that use the **AutoMapper** library (typically your `*.Application` project). You can change it from `netstandard2.0` to `netstandard2.1` or `net6` if needed. Please see [#12189](https://github.com/abpframework/abp/pull/12189) for more info.
## See Also
* [Official blog post for the 5.3 release](https://blog.abp.io/abp/ABP.IO-Platform-5.3-RC-Has-Been-Published)

1
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). 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).
- [5.2 to 5.3](Abp-5_3.md)
- [5.1 to 5.2](Abp-5_2.md) - [5.1 to 5.2](Abp-5_2.md)
- [4.x to 5.0](Abp-5_0.md) - [4.x to 5.0](Abp-5_0.md)
- [4.2 to 4.3](Abp-4_3.md) - [4.2 to 4.3](Abp-4_3.md)

240
docs/en/Modules/OpenIddict.md

@ -0,0 +1,240 @@
## ABP OpenIddict Modules
## How to Install
TODO:
## User Interface
This module implements the domain logic and database integrations, but not provides any UI. Management UI is useful if you need to add applications and scopes on the fly. In this case, you may build the management UI yourself or consider to purchase the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module.
## Relations to Other Modules
This module is based on the [Identity Module](Identity.md) and have an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer) with the [Account Module](Account.md).
## The module
### Demo projects
In the module's `app` directory there are six projects(including `angular`)
* `OpenIddict.Demo.Server`: An abp application with integrated modules (has two `clients` and a `scope`).
* `OpenIddict.Demo.API`: ASP NET Core API application using JwtBearer authentication
* `OpenIddict.Demo.Client.Mvc`: ASP NET Core MVC application using `OpenIdConnect` for authentication
* `OpenIddict.Demo.Client.Console`: Use `IdentityModel` to test OpenIddict's various endpoints, and call the api of `OpenIddict.Demo.API`
* `OpenIddict.Demo.Client.BlazorWASM:` ASP NET Core Blazor application using `OidcAuthentication` for authentication
* `angular`: An angular application that integrates the abp ng modules and uses oauth for authentication
#### How to run?
Confirm the connection string of `appsettings.json` in the `OpenIddict.Demo.Server` project. Running the project will automatically create the database and initialize the data.
After running the `OpenIddict.Demo.API` project, then you can run the rest of the projects to test.
### Domain module
There are four main entities included in this module.
* OpenIddictApplication: **Represents applications(client)**
* OpenIddictScope: **Represents scopes**
* OpenIddictAuthorization: **Represents authorizations, Track of logical chains of tokens and user consent..**
* OpenIddictToken: **Represents various tokens.**
Domain also implements four store interfaces in OpenIddict, OpenIddict uses store to manage entities, corresponding to the above four entities, Custom entity repository is used in the store.
```cs
//Manager
OpenIddictApplicationManager
OpenIddictScopeManager
OpenIddictAuthorizationManager
OpenIddictTokenManager
//Store
IOpenIddictApplicationStore
IOpenIddictScopeStore
IOpenIddictAuthorizationStore
IOpenIddictTokenStore
//Repository
IOpenIddictApplicationRepository
IOpenIddictScopeRepository
IOpenIddictAuthorizationRepository
IOpenIddictTokenRepository
```
We enabled most of OpenIddict's features in the `AddOpenIddict` method, You can change OpenIddict's related builder options via `PreConfigure`.
```cs
PreConfigure<OpenIddictBuilder>(builder =>
{
//builder
});
PreConfigure<OpenIddictCoreBuilder>(builder =>
{
//builder
});
PreConfigure<OpenIddictServerBuilder>(builder =>
{
//builder
});
```
#### AbpOpenIddictOptions
`UpdateAbpClaimTypes(default: true)`: Updates AbpClaimTypes to be compatible with identity server claims.
`AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate.
You can also change this options via `PreConfigure`.
#### Automatically removing orphaned tokens/authorizations
There is a background task in the `Domain` module (`enabled by default`) that automatically removes orphaned tokens/authorizations, you can configure `TokenCleanupOptions` to manage it.
### ASP NET Core module
This module integrates ASP NET Core, with built-in MVC controllers for four protocols. It uses OpenIddict's [Pass-through mode](https://documentation.openiddict.com/guides/index.html#pass-through-mode).
```cs
AuthorizeController -> connect/authorize
TokenController -> connect/token
LogoutController -> connect/logout
UserInfoController -> connect/userinfo
```
> We will implement the related functions of **device flow** in the PRO module..
#### How to control claims in access_token and id_token
You can use the [Claims Principal Factory](https://docs.abp.io/en/abp/latest/Authorization#claims-principal-factory) to add/remove claims to the `ClaimsPrincipal`.
The `AbpDefaultOpenIddictClaimDestinationsProvider` service will add `Name`, `Email` and `Role` types of Claims to `access_token` and `id_token`, other claims are only added to `access_token` by default, and remove the `SecurityStampClaimType` secret claim of `Identity`.
You can create a service that inherits from `IAbpOpenIddictClaimDestinationsProvider` and add it to DI to fully control the destinations of claims
```cs
public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvider, ITransientDependency
{
public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context)
{
// ...
return Task.CompletedTask;
}
}
Configure<AbpOpenIddictClaimDestinationsOptions>(options =>
{
options.ClaimDestinationsProvider.Add<MyClaimDestinationsProvider>();
});
```
For detailed information, please refer to: [OpenIddict claim destinations](https://documentation.openiddict.com/configuration/claim-destinations.html)
#### About Validation
The `OpenIddict.Validation.AspNetCore` and `OpenIddict.Validation` are not integrated in the module, we use the authentication component provided by Microsoft. If you are more familiar with it, you can use it in your project.
### EF Core module
Implements the above four repository interfaces.
### MongoDB module
Implements the above four repository interfaces.
## OpenIddict
### Documentation
For more details about OpenIddict, please refer to its official documentation and Github.
https://documentation.openiddict.com
https://github.com/openiddict/openiddict-core#resources
### Token encryption
https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html
> By default, OpenIddict enforces encryption for all the token types it supports. While this enforcement cannot be disabled for authorization codes, refresh tokens and device codes for security reasons, it can be relaxed for access tokens when integration with third-party APIs/resource servers is desired. Access token encryption can also be disabled if the resource servers receiving the access tokens don't fully support JSON Web Encryption.
```cs
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.DisableAccessTokenEncryption();
});
```
An example of using `SecurityKey`
> In production, it is recommended to use two RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing.
```cs
// In OpenIddict Server
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.AddSigningKey(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Abp_OpenIddict_Demo_C40DBB176E78")));
builder.AddEncryptionKey(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Abp_OpenIddict_Demo_87E33FC57D80")));
});
//In Client AddJwtBearer
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//Other configuration
options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Abp_OpenIddict_Demo_C40DBB176E78"));
options.TokenValidationParameters.TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Abp_OpenIddict_Demo_87E33FC57D80"));
});
```
### PKCE
https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html
### Request/Response process
I will briefly introduce the principle of OpenIddict so that everyone can quickly understand it.
The `OpenIddict.Server.AspNetCore` adds an authentication scheme(`Name: OpenIddict.Server.AspNetCore, handler: OpenIddictServerAspNetCoreHandler`) and implements the `IAuthenticationRequestHandler` interface.
It will be executed first in `AuthenticationMiddleware` and can short-circuit the current request. Otherwise, `DefaultAuthenticateScheme` will be called and continue to execute the pipeline.
`OpenIddictServerAspNetCoreHandler` will call various built-in handlers(Handling requests and responses), And the handler will process according to the context or skip logic that has nothing to do with it.
Example a token request:
```
POST /connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=password&
client_id=AbpApp&
client_secret=1q2w3e*&
username=admin&
password=1q2w3E*&
scope=AbpAPI offline_access
```
This request will be processed by various handlers. They will confirm the endpoint type of the request, check `http/https`, verify that the request parameters (`client. scope etc`) are valid and exist in the database, etc. Various protocol checks. And build a `OpenIddictRequest` object, If there are any errors, the response content may be set and directly short-circuit the current request.
If everything is ok, the request will go to our processing controller(eg `TokenController`), we can get an `OpenIddictRequest` from the http request at this time. The rest of our work will be based on this object.
We may check the `username` and `password` in the request. If it is correct we create a `ClaimsPrincipal` object and return a `SignInResult`, which uses the `OpenIddict.Validation.AspNetCore` authentication scheme name, will calls `OpenIddictServerAspNetCoreHandler` for processing.
`OpenIddictServerAspNetCoreHandler` do some checks to generate json and replace the http response content.
The `ForbidResult` `ChallengeResult` are all the above types of processing.
If you need to customize OpenIddict, you need to replace/delete/add new handlers and make it execute in the correct order.
Please refer to:
https://documentation.openiddict.com/guides/index.html#events-model
## Sponsor
Please consider sponsoring this project: https://github.com/sponsors/kevinchalet

2
docs/en/Redis-Cache.md

@ -21,7 +21,7 @@ abp add-package Volo.Abp.Caching.StackExchangeRedis
## Configuration ## Configuration
Volo.Abp.Caching.StackExchangeRedis package automatically gets the redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`: Volo.Abp.Caching.StackExchangeRedis package automatically gets the Redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`:
````js ````js
"Redis": { "Redis": {

4
docs/en/Repositories.md

@ -164,6 +164,10 @@ If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method
> See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete. > See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
### Ensure Entities Exists
The `EnsureExistsAsync` extension method accepts entity id or entities query expression to ensure entities exist, otherwise, it will throw `EntityNotFoundException`.
## Other Generic Repository Types ## Other Generic Repository Types
Standard `IRepository<TEntity, TKey>` interface exposes the standard `IQueryable<TEntity>` and you can freely query using the standard LINQ methods. This is fine for most of the applications. However, some ORM providers or database systems may not support standard `IQueryable` interface. If you want to use such providers, you can't rely on the `IQueryable`. Standard `IRepository<TEntity, TKey>` interface exposes the standard `IQueryable<TEntity>` and you can freely query using the standard LINQ methods. This is fine for most of the applications. However, some ORM providers or database systems may not support standard `IQueryable` interface. If you want to use such providers, you can't rely on the `IQueryable`.

82
docs/en/Themes/LeptonXLite/angular.md

@ -0,0 +1,82 @@
# LeptonX Lite Angular UI
LeptonX Lite has implementation for the ABP Framework Angular Client. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/).
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/).
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes.
## Installation
To add `LeptonX-lite` into your project,
* Install `@abp/ng.theme.lepton-x`
`yarn add @abp/ng.theme.lepton-x@preview`
* Install `bootstrap-icons`
`yarn add bootstrap-icons`
* Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one.
Add the following style
```json
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
```
* Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts`
```js
import { ThemeLeptonXModule } from '@abp/ng.theme.lepton-x';
import { SideMenuLayoutModule } from '@abp/ng.theme.lepton-x/layouts';
@NgModule({
imports: [
// ...
// do not forget to remove ThemeBasicModule
// ThemeBasicModule.forRoot(),
ThemeLeptonXModule.forRoot(),
SideMenuLayoutModule.forRoot(),
],
// ...
})
export class AppModule {}
```
Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well:
```js
import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account';
@NgModule({
// ...
imports: [
// ...
AccountLayoutModule.forRoot(),
// ...
],
// ...
})
export class AppModule {}
```
To change the logos and brand color of `LeptonX`, simply add the following CSS to the `styles.scss`
```css
:root {
--lpx-logo: url('/assets/images/logo.png');
--lpx-logo-icon: url('/assets/images/logo-icon.png');
--lpx-brand: #edae53;
}
```
- `--lpx-logo` is used to place the logo in the menu.
- `--lpx-logo-icon` is a square icon used when the menu is collapsed.
- `--lpx-brand` is a color used throughout the application, especially on active elements.
### Server Side
In order to migrate to LeptonX on your server side projects (Host and/or IdentityServer projects), please follow the [Server Side Migration](mvc.md) document.

147
docs/en/Themes/LeptonXLite/blazor.md

@ -0,0 +1,147 @@
# LeptonX Lite Blazor UI
````json
//[doc-params]
{
"UI": ["Blazor", "BlazorServer"]
}
````
LeptonX Lite has implementation for the ABP Framework Blazor WebAssembly & Blazor Server. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/).
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/).
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes.
## Installation
{{if UI == "Blazor"}}
- Complete the [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **IdentityServer**_.
- Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme** package to your **Blazor WebAssembly** application.
```bash
dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme
```
- Remove **Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme** reference from the project since it's not necessary after switching to LeptonX Lite.
- Remove the old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to the **DependsOn** attribute.
```diff
[DependsOn(
// Remove BasicTheme module from DependsOn attribute
- typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule),
// Add LeptonX Lite module to DependsOn attribute
+ typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule),
)]
```
- Change startup App component with the LeptonX one.
```csharp
// Make sure the 'App' comes from 'Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite' namespace.
builder.RootComponents.Add<App>("#ApplicationContainer");
```
- Run the `abp bundle` command in your **Blazor** application folder.
{{end}}
{{if UI == "BlazorServer"}}
- Complete the [MVC Razor Pages Installation](mvc.md#installation) first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and **IdentityServer**_.
- Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application.
```bash
dotnet add package Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme
```
- Remove **Volo.Abp.AspNetCore.Components.Server.BasicTheme** reference from the project since it's not necessary after switching to LeptonX Lite.
- Remove old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsServerLeptonXLiteThemeModule** type to the **DependsOn** attribute.
```diff
[DependsOn(
// Remove BasicTheme module from DependsOn attribute
- typeof(AbpAspNetCoreComponentsServerBasicThemeModule),
// Add LeptonX Lite module to DependsOn attribute
+ typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule)
)]
```
- Replace `BlazorBasicThemeBundles` with `BlazorLeptonXLiteThemeBundles` in `AbpBundlingOptions`:
```diff
options.StyleBundles.Configure(
// Remove following line
- BlazorBasicThemeBundles.Styles.Global,
// Add following line instead
+ BlazorLeptonXLiteThemeBundles.Styles.Global,
bundle =>
{
bundle.AddFiles("/blazor-global-styles.css");
//You can remove the following line if you don't use Blazor CSS isolation for components
bundle.AddFiles("/MyProjectName.Blazor.styles.css");
});
```
- Update `_Host.cshtml` file. _(located under **Pages** folder by default.)_
- Add following usings to Locate **App** and **BlazorLeptonXLiteThemeBundles** classes.
```csharp
@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite
@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling
```
- Then replace script & style bundles as following:
```diff
// Remove following line
- <abp-style-bundle name="@BlazorBasicThemeBundles.Styles.Global" />
// Add following line instead
+ <abp-style-bundle name="@BlazorLeptonXLiteThemeBundles.Styles.Global" />
```
```diff
// Remove following line
- <abp-script-bundle name="@BlazorBasicThemeBundles.Scripts.Global" />
// Add following line instead
+ <abp-script-bundle name="@BlazorLeptonXLiteThemeBundles.Scripts.Global" />
```
{{end}}
---
## Customization
### Toolbars
LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class.
- `LeptonXLiteToolbars.Main`
- `LeptonXLiteToolbars.MainMobile`
```csharp
public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name == LeptonXLiteToolbars.Main)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent)));
}
if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent)));
}
return Task.CompletedTask;
}
```
{{if UI == "BlazorServer"}}
> _You can visit the [Toolbars Documentation](https://docs.abp.io/en/abp/latest/UI/Blazor/Toolbars) for better understanding._
{{end}}

74
docs/en/Themes/LeptonXLite/mvc.md

@ -0,0 +1,74 @@
# LeptonX Lite MVC UI
LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/).
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/).
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes.
## Installation
- Add **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application.
```bash
dotnet add package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite --prerelease
```
- Remove **Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic** reference from the project since it's not necessary after switching to LeptonX Lite.
- Make sure the old theme is removed and LeptonX is added in your Module class.
```diff
[DependsOn(
// Remove BasicTheme module from DependsOn attribute
- typeof(AbpAspNetCoreMvcUiBasicThemeModule),
// Add LeptonX Lite module to DependsOn attribute
+ typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule),
)]
```
- Replace `BasicThemeBundles` with `LeptonXLiteThemeBundles` in AbpBundlingOptions
```diff
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
// Remove following line
- BasicThemeBundles.Styles.Global,
// Add following line instead
+ LeptonXLiteThemeBundles.Styles.Global
bundle =>
{
bundle.AddFiles("/global-styles.css");
}
);
});
```
---
## Customization
### Toolbars
LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class.
- `LeptonXLiteToolbars.Main`
- `LeptonXLiteToolbars.MainMobile`
```csharp
public class MyProjectNameMainToolbarContributor : IToolbarContributor
{
public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name == LeptonXLiteToolbars.Main)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent)));
}
if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent)));
}
}
}
```

29
docs/en/UI/Angular/DateTime-Format-Pipe.md

@ -0,0 +1,29 @@
# DateTime Format Pipes
You can format date by Date pipe of angular.
Example
```html
<span> {{today | date 'dd/mm/yy'}}</span>
```
ShortDate, ShortTime and ShortDateTime format data like angular's data pipe but easier. Also the pipes get format from config service by culture.
# ShortDate Pipe
```html
<span> {{today | shortDatePipe }}</span>
```
# ShortTime Pipe
```html
<span> {{today | shortTimePipe }}</span>
```
# ShortDateTime Pipe
```html
<span> {{today | shortDateTimePipe }}</span>
```

12
docs/en/UI/Angular/Page-Toolbar-Extensions.md

@ -21,9 +21,9 @@ The following code prepares a constant named `identityToolbarActionContributors`
import { import {
eIdentityComponents, eIdentityComponents,
IdentityToolbarActionContributors, IdentityToolbarActionContributors
IdentityUserDto,
} from '@abp/ng.identity'; } from '@abp/ng.identity';
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ToolbarAction, ToolbarActionList } from '@abp/ng.theme.shared/extensions'; import { ToolbarAction, ToolbarActionList } from '@abp/ng.theme.shared/extensions';
const logUserNames = new ToolbarAction<IdentityUserDto[]>({ const logUserNames = new ToolbarAction<IdentityUserDto[]>({
@ -93,7 +93,7 @@ We need to have a component before we can pass it to the toolbar action contribu
```js ```js
// src/app/click-me-button.component.ts // src/app/click-me-button.component.ts
import { IdentityUserDto } from '@abp/ng.identity'; import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.theme.shared/extensions'; import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.theme.shared/extensions';
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
@ -127,9 +127,9 @@ The following code prepares a constant named `identityToolbarActionContributors`
import { import {
eIdentityComponents, eIdentityComponents,
IdentityToolbarActionContributors, IdentityToolbarActionContributors
IdentityUserDto,
} from '@abp/ng.identity'; } from '@abp/ng.identity';
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { ToolbarActionList, ToolbarComponent } from '@abp/ng.theme.shared/extensions'; import { ToolbarActionList, ToolbarComponent } from '@abp/ng.theme.shared/extensions';
import { ClickMeButtonComponent } from './click-me-button.component'; import { ClickMeButtonComponent } from './click-me-button.component';
@ -362,7 +362,7 @@ export function reorderUserContributors(
) { ) {
// drop "New User" button // drop "New User" button
const newUserActionNode = actionList.dropByValue( const newUserActionNode = actionList.dropByValue(
'AbpIdentity::NewUser', 'AbpIdentity::NewUser',
(action, text) => action['text'] === text, (action, text) => action['text'] === text,
); );

BIN
docs/en/_resources/Diagrams.pptx

Binary file not shown.

48
docs/en/docs-nav.json

@ -704,6 +704,10 @@
{ {
"text": "The Basic Theme", "text": "The Basic Theme",
"path": "UI/AspNetCore/Basic-Theme.md" "path": "UI/AspNetCore/Basic-Theme.md"
},
{
"text": "LeptonX Lite",
"path": "Themes/LeptonXLite/mvc.md"
} }
] ]
}, },
@ -817,6 +821,10 @@
"text": "The Basic Theme", "text": "The Basic Theme",
"path": "UI/Blazor/Basic-Theme.md" "path": "UI/Blazor/Basic-Theme.md"
}, },
{
"text": "LeptonX Lite",
"path": "Themes/LeptonXLite/blazor.md"
},
{ {
"text": "Branding", "text": "Branding",
"path": "UI/Blazor/Branding.md" "path": "UI/Blazor/Branding.md"
@ -1071,6 +1079,10 @@
{ {
"text": "The Basic Theme", "text": "The Basic Theme",
"path": "UI/Angular/Basic-Theme.md" "path": "UI/Angular/Basic-Theme.md"
},
{
"text": "LeptonX Lite",
"path": "Themes/LeptonXLite/angular.md"
} }
] ]
}, },
@ -1085,10 +1097,10 @@
{ {
"text": "Extensions", "text": "Extensions",
"items": [ "items": [
{ {
"text": "Overall", "text": "Overall",
"path": "UI/Angular/Extensions-Overall.md" "path": "UI/Angular/Extensions-Overall.md"
}, },
{ {
"text": "Entity Action Extensions", "text": "Entity Action Extensions",
"path": "UI/Angular/Entity-Action-Extensions.md" "path": "UI/Angular/Entity-Action-Extensions.md"
@ -1104,6 +1116,10 @@
{ {
"text": "Dynamic Form Extensions", "text": "Dynamic Form Extensions",
"path": "UI/Angular/Dynamic-Form-Extensions.md" "path": "UI/Angular/Dynamic-Form-Extensions.md"
},
{
"text": "Date, time and datetime format pipes",
"path": "UI/Angular/DateTime-Format-Pipe.md"
} }
] ]
} }
@ -1226,15 +1242,12 @@
"path": "Testing.md" "path": "Testing.md"
}, },
{ {
"text": "Samples", "text": "Deployment",
"path": "Deployment/Index.md",
"items": [ "items": [
{ {
"text": "All Samples", "text": "Deploying to a Clustered Environment",
"path": "Samples/Index.md" "path": "Deployment/Clustered-Environment.md"
},
{
"text": "Microservice Demo",
"path": "Samples/Microservice-Demo.md"
} }
] ]
}, },
@ -1276,6 +1289,10 @@
{ {
"text": "IdentityServer", "text": "IdentityServer",
"path": "Modules/IdentityServer.md" "path": "Modules/IdentityServer.md"
},
{
"text": "OpenIddict",
"path": "Modules/OpenIddict.md"
}, },
{ {
"text": "Permission Management", "text": "Permission Management",
@ -1295,6 +1312,19 @@
} }
] ]
}, },
{
"text": "Samples",
"items": [
{
"text": "All Samples",
"path": "Samples/Index.md"
},
{
"text": "Microservice Demo",
"path": "Samples/Microservice-Demo.md"
}
]
},
{ {
"text": "Release Information", "text": "Release Information",
"items": [ "items": [

BIN
docs/en/images/deployment-clustered.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/images/deployment-single-instance.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

248
docs/zh-Hans/CLI-New-Command-Samples.md

@ -0,0 +1,248 @@
# ABP CLI - 新解决方案命令示例
`abp new`命令基于abp模板创建abp解决方案或其他组件. [ABP CLI](CLI.md)有一些参数可以用于创建新的ABP解决方案. 在本文档中, 我们将向你展示一些创建新的解决方案的命令示例. 所有的项目名称都是`Acme.BookStore`. 目前, 唯一可用的移动端项目是`React Native`移动端应用程序. 可用的数据库提供程序有`Entity Framework Core`和`MongoDB`. 所有命令都以`abp new`开头.
## Angular
以下命令用于创建Angular UI项目:
* 在新文件夹中创建项目, **Entity Framework Core**, 非移动端应用程序:
````bash
abp new Acme.BookStore -u angular --mobile none --database-provider ef -csf
````
* 在新文件夹中创建项目, **Entity Framework Core**, 默认应用程序模板, **拆分Identity Server**:
```bash
abp new Acme.BookStore -t app -u angular -m none --separate-identity-server --database-provider ef -csf
```
* 在新文件夹中创建项目, **Entity Framework Core**, **自定义连接字符串**:
```bash
abp new Acme.BookStore -u angular -csf --connection-string Server=localhost;Database=MyDatabase;Trusted_Connection=True
```
* 在`C:\MyProjects\Acme.BookStore`中创建解决方案, **MongoDB**, 默认应用程序模板, 包含移动端项目:
```bash
abp new Acme.BookStore -u angular --database-provider mongodb --output-folder C:\MyProjects\Acme.BookStore
```
* 在新文件夹中创建项目, **MongoDB**, 默认应用程序模板, 不创建移动端应用程序, **拆分Identity Server**:
```bash
abp new Acme.BookStore -t app -u angular -m none --separate-identity-server --database-provider mongodb -csf
```
## MVC
以下命令用于创建MVC UI项目:
* 在新文件夹中创建项目, **Entity Framework Core**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef -csf
```
* 在新文件夹中创建项目, **Entity Framework Core**, **分层结构** (*Web和HTTP API层是分开的*), 不创建移动端应用程序:
```bash
abp new Acme.BookStore -u mvc --mobile none --tiered --database-provider ef -csf
```
* 在新文件夹中创建项目, **MongoDB**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider mongodb -csf
```
* 在新文件夹中创建项目, **MongoDB**, **分层结构**:
```bash
abp new Acme.BookStore -u mvc --tiered --database-provider mongodb -csf
```
## Blazor
以下命令用于创建Blazor项目:
* **Entity Framework Core**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -t app -u blazor --mobile none
```
* **Entity Framework Core**, **拆分Identity Server**, 包含移动端应用程序:
```bash
abp new Acme.BookStore -u blazor --separate-identity-server
```
* 在新文件夹中创建项目, **MongoDB**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -u blazor --database-provider mongodb --mobile none -csf
```
## Blazor Server
以下命令用于创建Blazor项目:
* **Entity Framework Core**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -t app -u blazor-server --mobile none
```
* **Entity Framework Core**, **拆分Identity Server**, **拆分API Host**, 包含移动端应用程序:
```bash
abp new Acme.BookStore -u blazor-server --tiered
```
* 在新文件夹中创建项目, **MongoDB**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -u blazor --database-provider mongodb --mobile none -csf
```
## 无UI
在默认应用程序模板中, 始终有一个前端项目. 在这个选项中没有前端项目. 它有一个`HttpApi.Host`项目为你的HTTP WebAPI提供服务. 这个选项适合在你想创建一个WebAPI服务时使用.
* 在新文件夹中创建项目, **Entity Framework Core**, 拆分Identity Server:
```bash
abp new Acme.BookStore -u none --separate-identity-server -csf
```
* **MongoDB**, 不创建移动端应用程序:
```bash
abp new Acme.BookStore -u none --mobile none --database-provider mongodb
```
## 控制台应用程序
这是一个基于.NET控制台应用程序的模板, 集成了ABP模块架构. 要创建控制台应用程序, 请使用以下命令:
* 项目由以下文件组成: `Acme.BookStore.csproj`, `appsettings.json`, `BookStoreHostedService.cs`, `BookStoreModule.cs`, `HelloWorldService.cs` 和 `Program.cs`.
```bash
abp new Acme.BookStore -t console -csf
```
## 模块
模块是主项目使用的可重用子应用程序. 如果你正在构建微服务解决方案, 使用ABP模块是最佳方案. 由于模块不是最终的应用程序, 每个模块都有前端UI项目和数据库提供程序. 模块模板带有MVC UI, 可以在没有最终解决方案的情况下进行开发. 但是, 如果要在最终解决方案下开发模块, 可以添加`--no-ui`参数来去除MVC UI项目.
* 包含前端: `MVC`, `Angular`, `Blazor`. 包含数据库提供程序: `Entity Framework Core`, `MongoDB`. 包含MVC启动项目.
```bash
abp new Acme.IssueManagement -t module
```
* 与上面相同, 但不包括MVC启动项目.
```bash
abp new Acme.IssueManagement -t module --no-ui
```
* 创建模块并将其添加到解决方案中
```bash
abp new Acme.IssueManagement -t module --add-to-solution-file
```
## 从特定版本创建解决方案
创建解决方案时, 它总是使用最新版本创建. 要从旧版本创建项目, 可以使用`--version`参数.
* 使用v3.3.0版本创建解决方案, 包含Angular UI和Entity Framework Core.
```bash
abp new Acme.BookStore -t app -u angular -m none --database-provider ef -csf --version 3.3.0
```
要获取ABP版本列表, 请查看以下链接: https://www.nuget.org/packages/Volo.Abp.Core/
## 从自定义模板创建
ABP CLI使用默认的[应用程序模板](https://github.com/abpframework/abp/tree/dev/templates/app)创建项目. 如果要从自定义模板创建新的解决方案, 可以使用参数`--template-source`.
* 在`c:\MyProjects\templates\app`目录中使用模板, MVC UI, Entity Framework Core, 不创建移动端应用程序.
```bash
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef --template-source "c:\MyProjects\templates\app"
```
* 除了此命令从URL `https://myabp.com/app-template.zip` 检索模板之外, 与上一个命令相同.
```bash
abp new Acme.BookStore -t app -u mvc --mobile none --database-provider ef --template-source https://myabp.com/app-template.zip
```
## 创建预览版本
ABP CLI始终使用最新版本. 要从预览(RC)版本创建解决方案, 请添加`--preview`参数.
* 在新文件夹中创建项目, Blazor UI, Entity Framework Core, 不创建移动端应用程序, **使用最新版本**:
```bash
abp new Acme.BookStore -t app -u blazor --mobile none -csf --preview
```
## 选择数据库管理系统
默认的数据库管理系统是 `Entity Framework Core` / ` SQL Server`. 你可以通过使用`--database-management-system`参数选择DBMS. [可用的值](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseManagementSystem.cs) 包括 `SqlServer`, `MySQL`, `SQLite`, `Oracle`, `Oracle-Devart`, `PostgreSQL`. 默认值是 `SqlServer`.
* 在新文件夹中创建项目, Angular UI, **PostgreSQL** 数据库:
```bash
abp new Acme.BookStore -u angular --database-management-system PostgreSQL -csf
```
## 使用静态HTTP端口
ABP CLI始终为项目分配随机端口. 如果需要保留默认端口并且创建解决方案始终使用相同的HTTP端口, 请添加参数`--no-random-port`.
* 在新文件夹中创建项目, MVC UI, Entity Framework Core, **静态端口**:
```bash
abp new Acme.BookStore --no-random-port -csf
```
## 引用本地ABP框架
在ABP解决方案中, 默认情况下从NuGet引用ABP库. 有时, 你需要在本地将ABP库引用到你的解决方案中. 这利于调试框架本身. 本地ABP框架的根目录必须有`Volo.Abp.sln`文件. 你可以将以下目录的内容复制到你的文件系统中
* MVC UI, Entity Framework Core, **引用本地的ABP库**:
本地路径必须是ABP存储库的根目录.
如果`C:\source\abp\framework\Volo.Abp.sln`是你的框架解决方案的路径, 那么你必须设置`--abp-path`参数值为`C:\source\abp`.
```bash
abp new Acme.BookStore --local-framework-ref --abp-path C:\source\abp
```
**输出**:
如下所示, 引用本地ABP框架库项目.
```xml
<ItemGroup>
<ProjectReference Include="C:\source\abp\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="C:\source\abp\framework\src\Volo.Abp.AspNetCore.Serilog\Volo.Abp.AspNetCore.Serilog.csproj" />
<ProjectReference Include="C:\source\abp\framework\src\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj" />
<ProjectReference Include="..\Acme.BookStore.Application\Acme.BookStore.Application.csproj" />
<ProjectReference Include="..\Acme.BookStore.HttpApi\Acme.BookStore.HttpApi.csproj" />
<ProjectReference Include="..\Acme.BookStore.EntityFrameworkCore\Acme.BookStore.EntityFrameworkCore.csproj" />
</ItemGroup>
```
## 另请参阅
* [ABP CLI文档](CLI.md)

4
docs/zh-Hans/CLI.md

@ -78,6 +78,8 @@ abp new Acme.BookStore
* Acme.BookStore是解决方案的名称. * Acme.BookStore是解决方案的名称.
* 常见的命名方式类似于 *YourCompany.YourProject*. 不过你可以使用自己喜欢的方式,如 *YourProject* (单级命名空间) 或 *YourCompany.YourProduct.YourModule* (三级命名空间). * 常见的命名方式类似于 *YourCompany.YourProject*. 不过你可以使用自己喜欢的方式,如 *YourProject* (单级命名空间) 或 *YourCompany.YourProduct.YourModule* (三级命名空间).
参阅[ABP CLI 创建新解决方案示例](CLI-New-Command-Samples.md)查看更多示例.
#### Options #### Options
* `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有: * `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有:
@ -407,4 +409,4 @@ abp install-libs [options]
#### Options #### Options
* ```--working-directory``` 或 ```-wd```: 指定工作目录, 当执行目录不包含项目文件时会很有用. * ```--working-directory``` 或 ```-wd```: 指定工作目录, 当执行目录不包含项目文件时会很有用.

2
docs/zh-Hans/Connection-Strings.md

@ -7,7 +7,7 @@ ABP框架的设计是[模块化](Module-Development-Basics.md), [微服务兼容
它还支持混合场景; 它还支持混合场景;
* 允许将模块分组到数据库 (所有的模块分组到一个共享数据库, 两个模块使用数据库A, 3个模块使用数据库B, 一个数据库使用数据库C其余的数据库使用数据库D...等.) * 允许将模块分组到数据库 (所有的模块分组到一个共享数据库, 两个模块使用数据库A, 三个模块使用数据库B, 一个模块使用数据库C其余的模块使用数据库D...等.)
* 允许将租户分组到数据库中,像模块一样. * 允许将租户分组到数据库中,像模块一样.
* 允许为每个租户每个模块分离数据库 (数据库过多会增加维护成本,但ABP框架支持这种需求). * 允许为每个租户每个模块分离数据库 (数据库过多会增加维护成本,但ABP框架支持这种需求).

99
docs/zh-Hans/Deployment/Clustered-Environment.md

@ -0,0 +1,99 @@
# 部署到群集环境
本文档介绍了在将应用程序部署到**多个应用程序实例同时运行**的集群环境中时应注意的内容, 并解释了如何在基于ABP的应用程序中处理这些内容.
> 无论你使用的是单体式应用程序还是微服务解决方案, 本文档均有效. 适用于一个流程. 应用程序可以是单体式web应用程序、微服务解决方案中的服务、控制台应用程序或其他类型的可执行进程.
>
> 例如, 如果你将应用程序部署到Kubernetes并把应用程序或服务在多个POD中运行, 那么应用程序或服务将在集群环境中运行.
## 了解集群环境
> 如果你已经熟悉集群部署和负载均衡器, 可以跳过本节.
### 单实例部署
考虑作为**单个实例**部署的应用程序, 如下图所示:
![deployment-single-instance](../images/deployment-single-instance.png)
浏览器和其他客户端应用程序可以直接向应用程序发出HTTP请求. 你可以在客户端和应用程序之间放置一个web服务器(例如IIS或NGINX), 但仍有一个应用程序实例在单个服务器或容器中运行. 单实例的配置**限于规模**, 因为它在一台服务器上运行, 并且你受到服务器容量的限制.
### 集群部署
**集群部署**是在一台或多台服务器上**同时运行**应用程序**多个实例**的方式. 通过这种方式, 不同的实例可以满足不同的请求, 并且可以通过在系统中添加新服务器来扩展. 下图显示了集群使用**负载均衡器**的典型实现:
![deployment-clustered](../images/deployment-clustered.png)
### 负载均衡器
[负载均衡器](https://en.wikipedia.org/wiki/Load_balancing_(computing)) 有很多特性, 但它们基本上会将**传入的HTTP请求转发**给应用程序的实例, 并将响应返回给客户端应用程序.
负载平衡器可以使用不同的算法来选择应用程序实例, 同时确定用于传递传入请求的应用程序实例. **循环**是最简单、最常用的算法之一. 请求被轮流传递到应用程序实例. 第一个实例得到第一个请求, 第二个实例得到第二个请求, 依此类推. 在所有实例都被使用之后, 它返回到第一个实例, 并且下一个请求的算法也是类似的.
### 潜在问题
一旦应用程序的多个实例并行运行, 你应该仔细考虑以下内容:
* 当你有多个实例时, 存储在应用程序 **内存中的任何状态(数据)** 都将成为问题. 存储在应用程序实例内存中的状态可能在下一个请求中不可用, 因为下一个请求将由不同的应用程序实例处理. 虽然有一些解决方案(比如粘性会话)可以解决这个问题, 但如果你想在集群、容器或云中运行应用程序, **最好将其设计为无状态**.
* **内存缓存** 是一种内存状态, 不应在集群应用程序中使用. 你应该使用**分布式缓存**.
* 你不应该在**本地文件系统**中存储应用程序所有实例都可以使用的数据. 不同的应用程序实例可能在不同的容器或服务器中运行, 并且它们可能无法访问同一个文件系统. 你可以使用**云或外部存储提供商**作为解决方案.
* 如果你有**后台工作者**或**作业队列管理器**, 则应小心, 因为多个实例可能会尝试执行同一作业或同时执行同一工作. 因此, 你可能会多次完成相同的工作, 或者在尝试访问和更改相同的资源时可能会出现很多错误.
集群部署可能会有更多问题, 但这些是最常见的问题. ABP被设计为与集群部署场景兼容. 以下各节介绍了将基于ABP的应用程序部署到集群环境时应执行的操作.
## 切换分布式缓存
ASP.NET Core提供了不同类型的缓存功能. [内存缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory)将对象存储在本地服务器的内存中, 并且仅对存储该对象的应用程序可用. 集群环境中的非粘性会话应使用[分布式缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed), 除了一些特定场景(例如, 你可以将本地CSS文件缓存到内存中. 它是只读数据, 在所有应用程序实例中都是相同的. 出于性能原因, 你可以将其缓存到内存中, 而不会出现任何问题).
[ABP的分布式缓存](../Caching.md)扩展了[ASP.NET Core的分布式缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)的基础设施. 默认情况下, 它在内存中工作. 当你要将应用程序部署到集群环境时, 应该配置实际的分布式缓存提供程序.
> 即使应用程序不直接使用`IDistributedCache`, 也应该为集群部署配置缓存提供程序. 因为ABP框架和预构建的[应用程序模块](../Modules/Index.md)正在使用分布式缓存.
ASP.NET Core提供了可以用作分布式缓存提供程序的多种集成, 如[Redis](https://redis.io/)和[NCache](https://www.alachisoft.com/ncache/). 你可以按照[微软文档](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)了解如何在应用程序中使用它们.
如果你决定使用Redis作为分布式缓存提供程序, **请遵循[ABP的Redis缓存集成文档](../Redis-Cache.md)** 了解将其安装到应用程序并配置Redis所需遵循的步骤.
> 根据你在创建新ABP解决方案时的偏好, Redis缓存可能会预先安装在你的解决方案中. 例如, 如果你在MVC UI中选择了*Tiered*选项, Redis缓存将进行预装. 因为, 在这种情况下, 解决方案中有两个应用程序, 它们应该使用相同的缓存源来保持一致.
## 使用合适的BLOB存储提供程序
如果你在[文件系统提供程序](../Blob-Storing-File-System.md)中使用了ABP的[BLOB存储](../Blob-Storing.md)功能, 则应该在集群环境中使用另一个提供程序, 因为文件系统提供程序使用应用程序的本地文件系统.
[数据库BLOB提供程序](../Blob-Storing-Database)是最简单的方法, 因为它使用应用程序的主数据库(或另一个数据库, 如果你配置的话)来存储BLOB. 但是, 你应该记住, BLOB是大型对象, 可能会迅速增加数据库的大小.
> [ABP商业版](https://commercial.abp.io/)启动解决方案模板预装了数据库BLOB提供程序, 并将BLOB存储在应用程序的数据库中.
查看[BLOB Storing](../Blob-Storing.md)文档以查看所有可用的BLOB存储提供程序.
## 配置后台作业
ABP的[后台作业系统](../Background-Jobs.md)将要在后台执行的任务进行排队. 后台作业队列是持久性的, 排队的任务能够保证执行(如果失败, 将重新尝试).
ABP的默认后台作业管理器与集群环境兼容. 它使用[分布式锁](../Distributed-Locking.md)来确保一次只能在单个应用程序实例中执行作业. 请参阅下面的*配置分布式锁提供程序*部分, 了解如何为应用程序配置分布式锁提供程序, 以便默认后台作业管理器在集群环境中正常工作.
如果不想使用分布式锁提供程序, 可以使用以下选项:
* 停止所有应用程序实例中的后台作业管理器(将`AbpBackgroundJobOptions.IsJobExecutionEnabled`设置为`false`)只保留其中一个应用程序实例, 以便只有单个实例执行作业(而其他应用程序实例仍可以对作业进行排队).
* 在所有应用程序实例中停止后台作业管理器(将`AbpBackgroundJobOptions.IsJobExecutionEnabled`设置为`false`), 并创建一个专用的应用程序(可能是在自己的容器中运行的控制台应用程序或在后台运行的Windows服务)来执行所有后台作业. 如果你的后台作业占用大量系统资源(CPU、RAM或磁盘), 那么这是一个不错的选择, 这样你就可以将该后台应用程序部署到专用服务器上, 并且后台作业不会影响应用程序的性能.
> 如果你使用的是外部后台作业集成(例如[Hangfire](../Background-Workers-Hangfire.md)或[Quartz](../Background-Workers-Quartz.md))而不是默认的后台作业管理器, 请参阅提供程序的文档, 了解如何为集群环境配置它.
## 配置分布式锁提供程序
ABP通过[分布式锁](https://github.com/madelson/DistributedLock)库实现了一个抽象的分布式锁. 分布式锁用于控制多个应用程序对共享资源的并发访问, 以防止由于并发写入而导致资源损坏. ABP框架和一些预构建的[应用程序模块](../Modules/Index.md)出于一些原因正在使用分布式锁.
但是, 分布式锁系统默认在进程中工作. 这意味着它实际上不是分布式的, 除非配置分布式锁提供程序. 因此, 如果尚未配置应用程序的提供程序, 请按照[分布式锁](../Distributed-Locking.md)文档为其配置提供程序.
## 实现后台工作者
ASP.NET Core[托管服务](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services), ABP提供[后台工作者](../Background-Workers.md)在应用程序的后台线程中执行任务.
如果你的应用程序有在后台运行的任务, 你应该注意它们在集群环境中的行为, 尤其是在后台任务使用相同资源的情况下. 你应该设计后台任务, 以便它们在集群环境中继续正常工作.
假设SaaS应用程序中的后台工作者检查用户订阅, 并在订阅续订日期临近时发送电子邮件. 如果后台任务在多个应用程序实例中运行, 可能会多次向某些用户发送同一封电子邮件, 这会影响他们.
我们建议你使用以下方法之一来解决此问题:
* 实现你的后台工作者, 以便他们在集群环境中工作时不会出现任何问题. 使用[分布式锁](../Distributed-Locking.md)来确保并发控制是一种方法. 应用程序实例中的后台工作者可能会处理分布式锁, 因此其他应用程序实例中的工作者将等待该锁. 这样, 只有一个工作者在实际工作, 而其他的则在等待. 如果你实现了这一点, 你的后台工作者就可以安全地运行, 不必关心应用程序是如何部署的.
* 停止所有应用程序实例中的后台工作者(将`AbpBackgroundWorkerOptions.IsEnabled`设置为`false`), 只保留其中一个应用程序实例, 因此只有单个实例运行这些后台工作者.
* 停止所有应用程序实例中的后台工作者(将`AbpBackgroundWorkerOptions.IsEnabled`设置为`false`), 并创建一个专用的应用程序(可能是在自己的容器中运行的控制台应用程序或在后台运行的Windows服务)来执行所有后台任务. 如果你的后台工作者消耗大量系统资源(CPU、RAM或磁盘), 那么这是一个不错的选择, 这样你就可以将该后台应用程序部署到专用服务器上, 并且你的后台任务不会影响应用程序的性能.

9
docs/zh-Hans/Deployment/Index.md

@ -0,0 +1,9 @@
# 部署
部署ABP应用程序与部署其他.NET或ASP.NET Core应用程序并没有什么不同. 你可以将其部署到云服务提供商(例如Azure、AWS、Google)或内部部署服务器、IIS或任何其他web服务器. ABP的文档中没有太多关于部署的信息. 你可以参考提供商的文档.
但是, 在部署应用程序时, 有些主题是你应该注意的. 其中大多数是一般的软件部署注意事项, 但你应该了解如何在基于ABP的应用程序中处理它们. 我们为此准备了指南, 建议你在设计部署配置之前仔细阅读这些指南.
## 指南
* [部署到群集环境](Clustered-Environment.md): 讲解了当你希望同时运行应用程序的多个实例时, 如何来配置应用程序.

2
docs/zh-Hans/Distributed-Event-Bus-Kafka-Integration.md

@ -20,7 +20,7 @@
这是配置Kafka设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量). 这是配置Kafka设置最简单的方法. 它也非常强大,因为你可以使用[由AspNet Core支持](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)的任何其他配置源(如环境变量).
**示例:最小化配置与默认配置连接到本地的Kafka服务器** **示例:使用默认配置连接到本地Kafka服务器的最小配置**
````json ````json

2
docs/zh-Hans/Distributed-Event-Bus-Rebus-Integration.md

@ -1,7 +1,9 @@
# 分布式事件总线Rebus集成 # 分布式事件总线Rebus集成
> 本文解释了 **如何配置[Rebus](http://mookid.dk/category/rebus/)** 做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统. > 本文解释了 **如何配置[Rebus](http://mookid.dk/category/rebus/)** 做为分布式总线提供程序. 参阅[分布式事件总线文档](Distributed-Event-Bus.md)了解如何使用分布式事件总线系统.
## 安装 ## 安装
使用ABP CLI添加[Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus)NuGet包到你的项目: 使用ABP CLI添加[Volo.Abp.EventBus.Rebus](https://www.nuget.org/packages/Volo.Abp.EventBus.Rebus)NuGet包到你的项目:

110
docs/zh-Hans/Distributed-Locking.md

@ -0,0 +1,110 @@
# 分布式锁
分布式锁是一种管理多个应用程序访问同一资源的技术. 主要目的是同一时间只允许多个应用程序中的一个访问资源. 否则, 从不同的应用程序访问同一对象可能会破坏资源.
> ABP当前的分布式锁实现基于[DistributedLock](https://github.com/madelson/DistributedLock)库.
## 安装
你可以打开一个命令行终端并输入以下命令来安装[Volo.Abp.DistributedLocking](https://www.nuget.org/packages/Volo.Abp.DistributedLocking)到你的项目中:
````bash
abp add-package Volo.Abp.DistributedLocking
````
这个库提供了使用分布式锁系统所需的API, 但是, 在使用它之前, 你应该配置一个提供程序.
### 配置一个提供程序
[DistributedLock](https://github.com/madelson/DistributedLock)库对[Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md)和[ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md)提供[多种实现](https://github.com/madelson/DistributedLock#implementations).
例如, 如果你想使用[Redis provider](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md), 你应该将[DistributedLock.Redis](https://www.nuget.org/packages/DistributedLock.Redis) NuGet包添加到项目中, 然后将以下代码添加到ABP[模块](Module-Development-Basics.md)类的`ConfigureServices`方法中:
````csharp
using Medallion.Threading;
using Medallion.Threading.Redis;
namespace AbpDemo
{
[DependsOn(
typeof(AbpDistributedLockingModule)
//If you have the other dependencies, you should do here
)]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var connection = ConnectionMultiplexer
.Connect(configuration["Redis:Configuration"]);
return new
RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
}
}
}
````
此代码从[配置](Configuration.md)获取Redis连接字符串, 因此你可以将以下行添加到`appsettings.json`文件:
````json
"Redis": {
"Configuration": "127.0.0.1"
}
````
## 使用
有两种方法可以使用分布式锁API: ABP的`IAbpDistributedLock`抽象和[DistributedLock](https://github.com/madelson/DistributedLock)库的API.
### 使用IAbpDistributedLock服务
`IAbpDistributedLock`是ABP框架提供的一个用于简单使用分布式锁的服务.
**实例: 使用`IAbpDistributedLock.TryAcquireAsync`方法**
````csharp
using Volo.Abp.DistributedLocking;
namespace AbpDemo
{
public class MyService : ITransientDependency
{
private readonly IAbpDistributedLock _distributedLock;
public MyService(IAbpDistributedLock distributedLock)
{
_distributedLock = distributedLock;
}
public async Task MyMethodAsync()
{
await using (var handle =
await _distributedLock.TryAcquireAsync("MyLockName"))
{
if (handle != null)
{
// your code that access the shared resource
}
}
}
}
}
````
`TryAcquireAsync`可能无法获取锁. 如果无法获取锁, 则返回`null`. 在这种情况下, 你不应该访问资源. 如果句柄不为`null`, 则表示你已获得锁, 并且可以安全地访问资源.
`TryAcquireAsync`方法拥有以下参数:
* `name` (`string`, 必须): 锁的唯一名称. 不同的锁命名用于访问不同的资源.
* `timeout` (`TimeSpan`): 等待获取锁的超时值. 默认值为`TimeSpan.Zero`, 这意味着如果锁已经被另一个应用程序拥有, 它不会等待.
* `cancellationToken`: 取消令牌可在触发后取消操作.
### 使用DistributedLock库的API
ABP的`IAbpDistributedLock`服务非常有限, 主要用于ABP框架的内部使用. 对于你自己的应用程序, 可以使用DistributedLock库自己的API. 参见[文档](https://github.com/madelson/DistributedLock)详细信息.
## Volo.Abp.DistributedLocking.Abstractions库
如果你正在构建一个可重用的库或应用程序模块, 那么对于作为单个实例运行的简单应用程序, 你可能不希望为模块带来额外的依赖关系. 在这种情况下, 你的库可以依赖于[Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions)库, 它定义了`IAbpDistributedLock`服务, 并将其在进程内实现(实际上不是分布式的). 通过这种方式, 你的库可以在作为单个实例运行的应用程序中正常运行(没有分布式锁提供程序依赖项). 如果应用程序部署到[集群环境](Deployment/Clustered-Environment.md), 那么应用程序开发人员应该安装一个真正的分布式提供程序, 如*安装*部分所述.

5
docs/zh-Hans/Emailing.md

@ -22,8 +22,9 @@ abp add-package Volo.Abp.Emailing
### IEmailSender ### IEmailSender
[Inject](Dependency-Injection.md) 将 `IEmailSender` 注入任何服务并使用 `SendAsync` 方法发送电子邮件。 [注入](Dependency-Injection.md) `IEmailSender` 到任何服务并使用 `SendAsync` 方法发送电子邮件。
**Example**
**示例**
````csharp ````csharp
using System.Threading.Tasks; using System.Threading.Tasks;

2
docs/zh-Hans/Entities.md

@ -28,7 +28,7 @@ public class Book : Entity<Guid>
* 创建一个构造函数,获取ID作为参数传递给基类. * 创建一个构造函数,获取ID作为参数传递给基类.
* 如果没有为GUID Id赋值,**ABP框架会在保存时设置它**,但是在将实体保存到数据库之前最好在实体上有一个有效的Id. * 如果没有为GUID Id赋值,**ABP框架会在保存时设置它**,但是在将实体保存到数据库之前最好在实体上有一个有效的Id.
* 如果使用带参数的构造函数创建实体,那么还要创建一个 `private` 或 `protected` 构造函数. 当数据库提供程序从数据库读取你的实体时(反序列化时)将使用它. * 如果使用带参数的构造函数创建实体,那么还要创建一个 `private` 或 `protected` 构造函数. 当数据库提供程序从数据库读取你的实体时(反序列化时)将使用它.
* 不要使用 `Guid.NewGuid()` 来设置Id! 在创建实体的代码中**使用[`IGuidGenerator`服务](Guid-Generation.md)**传递Id参数. `IGuidGenerator`经过优化可以产生连续的GUID.这对于关系数据库中的聚集索引非常重要. * 不要使用 `Guid.NewGuid()` 来设置Id! 在创建实体的代码中**使用[`IGuidGenerator`服务](Guid-Generation.md)** 传递Id参数. `IGuidGenerator`经过优化可以产生连续的GUID.这对于关系数据库中的聚集索引非常重要.
示例实体: 示例实体:

6
docs/zh-Hans/Settings.md

@ -65,7 +65,7 @@ public class MySettingDefinitionProvider : SettingDefinitionProvider
} }
```` ````
> 使用常量作为设置名称是一种好习惯,ABP的包就是这样做的. `Abp.Mailing.Smtp`设置名称是在`EmailSettingNames`类(在Volo.Abp.Emailing名称空间中)定义的常量. > 使用常量作为设置名称是一种好习惯,ABP的包就是这样做的. `Abp.Mailing.Smtp.Host`设置名称是在`EmailSettingNames`类(在Volo.Abp.Emailing命名空间中)定义的常量.
## 读取设置值 ## 读取设置值
@ -141,7 +141,7 @@ var requireDigit = abp.setting.getBoolean('Abp.Identity.Password.RequireDigit');
* `TenantSettingValueProvider`: 获取当前租户的设置值(参阅 [多租户](Multi-Tenancy.md)文档). * `TenantSettingValueProvider`: 获取当前租户的设置值(参阅 [多租户](Multi-Tenancy.md)文档).
* `UserSettingValueProvider`: 获取当前用户的设置值(参阅 [当前用户](CurrentUser.md) 文档). * `UserSettingValueProvider`: 获取当前用户的设置值(参阅 [当前用户](CurrentUser.md) 文档).
> 设置回退系统从底部 (用户) 到 (默认) 方向起用用. > 设置回退系统从底部 (用户) 到 顶部(默认) 方向起作用.
全局,租户和用户设置值提供程序使用 `ISettingStore` 从数据源读取值(参见下面的小节). 全局,租户和用户设置值提供程序使用 `ISettingStore` 从数据源读取值(参见下面的小节).
@ -222,7 +222,7 @@ Configure<AbpSettingOptions>(options =>
`ISettingEncryptionService` 用于在设置定义的 `isencryption` 属性设置为 `true` 时加密/解密设置值. `ISettingEncryptionService` 用于在设置定义的 `isencryption` 属性设置为 `true` 时加密/解密设置值.
你可以在依赖项入系统中替换此服务,自定义实现加密/解密过程. 默认实现 `StringEncryptionService` 使用AES算法(参见字符串[加密文档](String-Encryption.md)学习更多). 你可以在依赖注入系统中替换此服务,自定义实现加密/解密过程. 默认实现 `StringEncryptionService` 使用AES算法(参见字符串[加密文档](String-Encryption.md)学习更多).
## 设置管理模块 ## 设置管理模块

28
docs/zh-Hans/docs-nav.json

@ -262,6 +262,10 @@
"text": "种子数据", "text": "种子数据",
"path": "Data-Seeding.md" "path": "Data-Seeding.md"
}, },
{
"text": "分布式锁",
"path": "Distributed-Locking.md"
},
{ {
"text": "虚拟文件系统", "text": "虚拟文件系统",
"path": "Virtual-File-System.md" "path": "Virtual-File-System.md"
@ -405,14 +409,16 @@
"path": "Entities.md" "path": "Entities.md"
}, },
{ {
"text": "值对象" "text": "值对象",
"path": "Value-Objects.md"
}, },
{ {
"text": "仓储", "text": "仓储",
"path": "Repositories.md" "path": "Repositories.md"
}, },
{ {
"text": "领域服务" "text": "领域服务",
"path": "Domain-Services.md"
}, },
{ {
"text": "规约", "text": "规约",
@ -694,6 +700,16 @@
"text": "测试", "text": "测试",
"path": "Testing.md" "path": "Testing.md"
}, },
{
"text": "部署",
"path": "Deployment/Index.md",
"items": [
{
"text": "部署到群集环境",
"path": "Deployment/Clustered-Environment.md"
}
]
},
{ {
"text": "示例", "text": "示例",
"items": [ "items": [
@ -794,7 +810,13 @@
"items": [ "items": [
{ {
"text": "CLI", "text": "CLI",
"path": "CLI.md" "path": "CLI.md",
"items": [
{
"text": "新命令示例",
"path": "CLI-New-Command-Samples.md"
}
]
}, },
{ {
"text": "API文档", "text": "API文档",

BIN
docs/zh-Hans/images/deployment-clustered.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/zh-Hans/images/deployment-single-instance.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/zh-Hans/images/email-settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

16
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs

@ -113,7 +113,7 @@ public class AbpInputTagHelperService : AbpTagHelperService<AbpInputTagHelper>
protected virtual TagHelper GetInputTagHelper(TagHelperContext context, TagHelperOutput output) protected virtual TagHelper GetInputTagHelper(TagHelperContext context, TagHelperOutput output)
{ {
if (TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>() != null) if (TryGetTextAreaAttribute(output) != null)
{ {
var textAreaTagHelper = new TextAreaTagHelper(_generator) var textAreaTagHelper = new TextAreaTagHelper(_generator)
{ {
@ -342,7 +342,7 @@ public class AbpInputTagHelperService : AbpTagHelperService<AbpInputTagHelper>
protected virtual void ConvertToTextAreaIfTextArea(TagHelperOutput tagHelperOutput) protected virtual void ConvertToTextAreaIfTextArea(TagHelperOutput tagHelperOutput)
{ {
var textAreaAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>(); var textAreaAttribute = TryGetTextAreaAttribute(tagHelperOutput);
if (textAreaAttribute == null) if (textAreaAttribute == null)
{ {
@ -362,6 +362,18 @@ public class AbpInputTagHelperService : AbpTagHelperService<AbpInputTagHelper>
} }
} }
protected virtual TextArea TryGetTextAreaAttribute(TagHelperOutput output)
{
var textAreaAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<TextArea>();
if (textAreaAttribute == null && output.Attributes.Any(a => a.Name == "text-area"))
{
return new TextArea();
}
return textAreaAttribute;
}
protected virtual TagHelperAttributeList GetInputAttributes(TagHelperContext context, TagHelperOutput output) protected virtual TagHelperAttributeList GetInputAttributes(TagHelperContext context, TagHelperOutput output)
{ {
var groupPrefix = "group-"; var groupPrefix = "group-";

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json

@ -1,8 +1,8 @@
{ {
"culture": "en", "culture": "en",
"texts": { "texts": {
"GivenTenantIsNotExist": "Given tenant does not exist: {0}", "GivenTenantIsNotExist": "Given tenant doesn't exist: {0}",
"GivenTenantIsNotAvailable": "Given tenant is not available: {0}", "GivenTenantIsNotAvailable": "Given tenant isn't available: {0}",
"Tenant": "Tenant", "Tenant": "Tenant",
"Switch": "switch", "Switch": "switch",
"Name": "Name", "Name": "Name",

8
framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj

@ -14,10 +14,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazorise" Version="1.0.2" /> <PackageReference Include="Blazorise" Version="1.0.4" />
<PackageReference Include="Blazorise.DataGrid" Version="1.0.2" /> <PackageReference Include="Blazorise.DataGrid" Version="1.0.4" />
<PackageReference Include="Blazorise.Snackbar" Version="1.0.2" /> <PackageReference Include="Blazorise.Snackbar" Version="1.0.4" />
<PackageReference Include="Blazorise.Components" Version="1.0.2" /> <PackageReference Include="Blazorise.Components" Version="1.0.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

100
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Commands.Services;
using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -12,14 +13,18 @@ namespace Volo.Abp.Cli.Commands;
public class CreateMigrationAndRunMigratorCommand : IConsoleCommand, ITransientDependency public class CreateMigrationAndRunMigratorCommand : IConsoleCommand, ITransientDependency
{ {
private readonly InitialMigrationCreator _initialMigrationCreator;
public const string Name = "create-migration-and-run-migrator"; public const string Name = "create-migration-and-run-migrator";
public ICmdHelper CmdHelper { get; } public ICmdHelper CmdHelper { get; }
public DotnetEfToolManager DotnetEfToolManager { get; }
public ILogger<CreateMigrationAndRunMigratorCommand> Logger { get; set; } public ILogger<CreateMigrationAndRunMigratorCommand> Logger { get; set; }
public CreateMigrationAndRunMigratorCommand(ICmdHelper cmdHelper) public CreateMigrationAndRunMigratorCommand(ICmdHelper cmdHelper, InitialMigrationCreator initialMigrationCreator, DotnetEfToolManager dotnetEfToolManager)
{ {
_initialMigrationCreator = initialMigrationCreator;
CmdHelper = cmdHelper; CmdHelper = cmdHelper;
DotnetEfToolManager = dotnetEfToolManager;
Logger = NullLogger<CreateMigrationAndRunMigratorCommand>.Instance; Logger = NullLogger<CreateMigrationAndRunMigratorCommand>.Instance;
} }
@ -39,110 +44,31 @@ public class CreateMigrationAndRunMigratorCommand : IConsoleCommand, ITransientD
throw new Exception("DbMigrator is not found!"); throw new Exception("DbMigrator is not found!");
} }
if (!IsDotNetEfToolInstalled()) await DotnetEfToolManager.BeSureInstalledAsync();
{
InstallDotnetEfTool();
}
var tenantDbContextName = FindTenantDbContextName(dbMigrationsFolder); var migrationsCreatedSuccessfully = await _initialMigrationCreator.CreateAsync(commandLineArgs.Target, !nolayers);
var dbContextName = tenantDbContextName != null ?
FindDbContextName(dbMigrationsFolder)
: null;
var migrationOutput = AddMigrationAndGetOutput(dbMigrationsFolder, dbContextName, "Migrations"); if (migrationsCreatedSuccessfully)
var tenantMigrationOutput = tenantDbContextName != null ?
AddMigrationAndGetOutput(dbMigrationsFolder, tenantDbContextName, "TenantMigrations")
: null;
if (CheckMigrationOutput(migrationOutput) && CheckMigrationOutput(tenantMigrationOutput))
{ {
if (nolayers) if (nolayers)
{ {
// Migration added successfully CmdHelper.RunCmd("dotnet run --migrate-database", Path.GetDirectoryName(Path.Combine(dbMigrationsFolder, "MyCompanyName.MyProjectName")));
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(Path.Combine(dbMigrationsFolder, "MyCompanyName.MyProjectName")) + "\" && dotnet run --migrate-database");
} }
else else
{ {
// Migration added successfully CmdHelper.RunCmd("dotnet run", Path.GetDirectoryName(dbMigratorProjectPath));
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigratorProjectPath) + "\" && dotnet run");
} }
await Task.CompletedTask; await Task.CompletedTask;
} }
else else
{ {
var exceptionMsg = "Migrations failed! A migration command didn't run successfully:" + var exceptionMsg = "Migrations failed! A migration command didn't run successfully.";
Environment.NewLine +
Environment.NewLine + migrationOutput +
Environment.NewLine +
Environment.NewLine + tenantMigrationOutput;
Logger.LogError(exceptionMsg); Logger.LogError(exceptionMsg);
throw new Exception(exceptionMsg); throw new Exception(exceptionMsg);
} }
} }
private string FindTenantDbContextName(string dbMigrationsFolder)
{
var tenantDbContext = Directory.GetFiles(dbMigrationsFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault() ??
Directory.GetFiles(dbMigrationsFolder, "*TenantDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault();
if (tenantDbContext == null)
{
return null;
}
return Path.GetFileName(tenantDbContext).RemovePostFix(".cs");
}
private string FindDbContextName(string dbMigrationsFolder)
{
var dbContext = Directory.GetFiles(dbMigrationsFolder, "*MigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantMigrationsDbContext.cs")) ??
Directory.GetFiles(dbMigrationsFolder, "*DbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantDbContext.cs"));
if (dbContext == null)
{
return null;
}
return Path.GetFileName(dbContext).RemovePostFix(".cs");
}
private string AddMigrationAndGetOutput(string dbMigrationsFolder, string dbContext, string outputDirectory)
{
var dbContextOption = string.IsNullOrWhiteSpace(dbContext)
? string.Empty
: $"--context {dbContext}";
var addMigrationCmd = $"cd \"{dbMigrationsFolder}\" && " +
$"dotnet ef migrations add Initial --output-dir {outputDirectory} {dbContextOption}";
return CmdHelper.RunCmdAndGetOutput(addMigrationCmd, out int exitCode);
}
private bool IsDotNetEfToolInstalled()
{
var output = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g", out int exitCode);
return output.Contains("dotnet-ef");
}
private static bool CheckMigrationOutput(string output)
{
return output == null || (output.Contains("Done.") &&
output.Contains("To undo this action") &&
output.Contains("ef migrations remove"));
}
private void InstallDotnetEfTool()
{
Logger.LogInformation("Installing dotnet-ef tool...");
CmdHelper.RunCmd("dotnet tool install --global dotnet-ef");
Logger.LogInformation("dotnet-ef tool is installed.");
}
private static string GetDbMigratorProjectPath(string dbMigrationsFolderPath) private static string GetDbMigratorProjectPath(string dbMigrationsFolderPath)
{ {
var srcFolder = Directory.GetParent(dbMigrationsFolderPath); var srcFolder = Directory.GetParent(dbMigrationsFolderPath);

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -32,8 +32,9 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
SolutionPackageVersionFinder solutionPackageVersionFinder, SolutionPackageVersionFinder solutionPackageVersionFinder,
ICmdHelper cmdHelper, ICmdHelper cmdHelper,
IInstallLibsService installLibsService, IInstallLibsService installLibsService,
AngularPwaSupportAdder angularPwaSupportAdder) AngularPwaSupportAdder angularPwaSupportAdder,
: base(connectionStringProvider, solutionPackageVersionFinder, cmdHelper, installLibsService, angularPwaSupportAdder) InitialMigrationCreator ınitialMigrationCreator)
: base(connectionStringProvider, solutionPackageVersionFinder, cmdHelper, installLibsService, angularPwaSupportAdder, ınitialMigrationCreator)
{ {
TemplateProjectBuilder = templateProjectBuilder; TemplateProjectBuilder = templateProjectBuilder;
TemplateInfoProvider = templateInfoProvider; TemplateInfoProvider = templateInfoProvider;
@ -79,6 +80,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
Logger.LogInformation($"'{projectName}' has been successfully created to '{projectArgs.OutputFolder}'"); Logger.LogInformation($"'{projectName}' has been successfully created to '{projectArgs.OutputFolder}'");
RunGraphBuildForMicroserviceServiceTemplate(projectArgs); RunGraphBuildForMicroserviceServiceTemplate(projectArgs);
await CreateInitialMigrationsAsync(projectArgs);
await RunInstallLibsForWebTemplateAsync(projectArgs); await RunInstallLibsForWebTemplateAsync(projectArgs);
ConfigurePwaSupportForAngular(projectArgs); ConfigurePwaSupportForAngular(projectArgs);

41
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs

@ -6,6 +6,7 @@ using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using NUglify.Helpers;
using Volo.Abp.Cli.ProjectModification; using Volo.Abp.Cli.ProjectModification;
using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Commands.Services; using Volo.Abp.Cli.Commands.Services;
@ -26,6 +27,7 @@ public abstract class ProjectCreationCommandBase
public ICmdHelper CmdHelper { get; } public ICmdHelper CmdHelper { get; }
public IInstallLibsService InstallLibsService { get; } public IInstallLibsService InstallLibsService { get; }
public AngularPwaSupportAdder AngularPwaSupportAdder { get; } public AngularPwaSupportAdder AngularPwaSupportAdder { get; }
public InitialMigrationCreator InitialMigrationCreator { get; }
public ILogger<NewCommand> Logger { get; set; } public ILogger<NewCommand> Logger { get; set; }
public ProjectCreationCommandBase( public ProjectCreationCommandBase(
@ -33,13 +35,15 @@ public abstract class ProjectCreationCommandBase
SolutionPackageVersionFinder solutionPackageVersionFinder, SolutionPackageVersionFinder solutionPackageVersionFinder,
ICmdHelper cmdHelper, ICmdHelper cmdHelper,
IInstallLibsService installLibsService, IInstallLibsService installLibsService,
AngularPwaSupportAdder angularPwaSupportAdder) AngularPwaSupportAdder angularPwaSupportAdder,
InitialMigrationCreator ınitialMigrationCreator)
{ {
ConnectionStringProvider = connectionStringProvider; ConnectionStringProvider = connectionStringProvider;
SolutionPackageVersionFinder = solutionPackageVersionFinder; SolutionPackageVersionFinder = solutionPackageVersionFinder;
CmdHelper = cmdHelper; CmdHelper = cmdHelper;
InstallLibsService = installLibsService; InstallLibsService = installLibsService;
AngularPwaSupportAdder = angularPwaSupportAdder; AngularPwaSupportAdder = angularPwaSupportAdder;
InitialMigrationCreator = ınitialMigrationCreator;
Logger = NullLogger<NewCommand>.Instance; Logger = NullLogger<NewCommand>.Instance;
} }
@ -337,6 +341,41 @@ public abstract class ProjectCreationCommandBase
} }
} }
protected async Task CreateInitialMigrationsAsync(ProjectBuildArgs projectArgs)
{
if (projectArgs.DatabaseProvider == DatabaseProvider.MongoDb)
{
return;
}
var efCoreProjectPath = string.Empty;
bool isLayeredTemplate;
switch (projectArgs.TemplateName)
{
case AppTemplate.TemplateName:
case AppProTemplate.TemplateName:
efCoreProjectPath = Directory.GetFiles(projectArgs.OutputFolder, "*EntityFrameworkCore.csproj", SearchOption.AllDirectories).FirstOrDefault();
isLayeredTemplate = true;
break;
case AppNoLayersTemplate.TemplateName:
case AppNoLayersProTemplate.TemplateName:
efCoreProjectPath = Directory.GetFiles(projectArgs.OutputFolder, "*.csproj", SearchOption.AllDirectories).FirstOrDefault();
isLayeredTemplate = false;
break;
default:
return;
}
if (string.IsNullOrWhiteSpace(efCoreProjectPath))
{
Logger.LogWarning("Couldn't find the project to create initial migrations!");
return;
}
await InitialMigrationCreator.CreateAsync(Path.GetDirectoryName(efCoreProjectPath), isLayeredTemplate);
}
protected void ConfigurePwaSupportForAngular(ProjectBuildArgs projectArgs) protected void ConfigurePwaSupportForAngular(ProjectBuildArgs projectArgs)
{ {
var isAngular = projectArgs.UiFramework == UiFramework.Angular; var isAngular = projectArgs.UiFramework == UiFramework.Angular;

43
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/DotnetEfToolManager.cs

@ -0,0 +1,43 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands.Services;
public class DotnetEfToolManager : ISingletonDependency
{
public ICmdHelper CmdHelper { get; }
public ILogger<DotnetEfToolManager> Logger { get; set; }
public DotnetEfToolManager(ICmdHelper cmdHelper)
{
CmdHelper = cmdHelper;
Logger = NullLogger<DotnetEfToolManager>.Instance;
}
public async Task BeSureInstalledAsync()
{
if (IsDotNetEfToolInstalled())
{
return;
}
InstallDotnetEfTool();
}
private bool IsDotNetEfToolInstalled()
{
var output = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g");
return output.Contains("dotnet-ef");
}
private void InstallDotnetEfTool()
{
Logger.LogInformation("Installing dotnet-ef tool...");
CmdHelper.RunCmd("dotnet tool install --global dotnet-ef");
Logger.LogInformation("dotnet-ef tool is installed.");
}
}

111
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs

@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands.Services;
public class InitialMigrationCreator : ITransientDependency
{
public ICmdHelper CmdHelper { get; }
public DotnetEfToolManager DotnetEfToolManager { get; }
public ILogger<InitialMigrationCreator> Logger { get; set; }
public InitialMigrationCreator(ICmdHelper cmdHelper, DotnetEfToolManager dotnetEfToolManager)
{
CmdHelper = cmdHelper;
DotnetEfToolManager = dotnetEfToolManager;
Logger = NullLogger<InitialMigrationCreator>.Instance;
}
public async Task<bool> CreateAsync(string targetProjectFolder, bool layeredTemplate = true)
{
if (targetProjectFolder == null || !Directory.Exists(targetProjectFolder))
{
Logger.LogError($"This path doesn't exist: {targetProjectFolder}");
return false;
}
Logger.LogInformation("Creating initial migrations...");
await DotnetEfToolManager.BeSureInstalledAsync();
var tenantDbContextName = FindTenantDbContextName(targetProjectFolder);
var dbContextName = tenantDbContextName != null ?
FindDbContextName(targetProjectFolder)
: null;
var migrationOutput = AddMigrationAndGetOutput(targetProjectFolder, dbContextName, "Migrations");
var tenantMigrationOutput = tenantDbContextName != null ?
AddMigrationAndGetOutput(targetProjectFolder, tenantDbContextName, "TenantMigrations")
: null;
var migrationSuccess = CheckMigrationOutput(migrationOutput) && CheckMigrationOutput(tenantMigrationOutput);
if (migrationSuccess)
{
Logger.LogInformation("Initial migrations are created.");
}
else
{
Logger.LogError("Creating initial migrations process is failed! Details:" + Environment.NewLine
+ migrationOutput + Environment.NewLine
+ tenantMigrationOutput + Environment.NewLine);
}
return migrationSuccess;
}
private string FindTenantDbContextName(string projectFolder)
{
var tenantDbContext = Directory.GetFiles(projectFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault() ??
Directory.GetFiles(projectFolder, "*TenantDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault();
if (tenantDbContext == null)
{
return null;
}
return Path.GetFileName(tenantDbContext).RemovePostFix(".cs");
}
private string FindDbContextName(string projectFolder)
{
var dbContext = Directory.GetFiles(projectFolder, "*MigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantMigrationsDbContext.cs")) ??
Directory.GetFiles(projectFolder, "*DbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantDbContext.cs"));
if (dbContext == null)
{
return null;
}
return Path.GetFileName(dbContext).RemovePostFix(".cs");
}
private string AddMigrationAndGetOutput(string dbMigrationsFolder, string dbContext, string outputDirectory)
{
var dbContextOption = string.IsNullOrWhiteSpace(dbContext)
? string.Empty
: $"--context {dbContext}";
var addMigrationCmd = $"dotnet ef migrations add Initial --output-dir {outputDirectory} {dbContextOption}";
return CmdHelper.RunCmdAndGetOutput(addMigrationCmd, out int exitCode, dbMigrationsFolder);
}
private static bool CheckMigrationOutput(string output)
{
return output == null || (output.Contains("Done.") &&
output.Contains("To undo this action") &&
output.Contains("ef migrations remove"));
}
}

28
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/MoveFileStep.cs

@ -0,0 +1,28 @@
using System.Linq;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
public class MoveFileStep : ProjectBuildPipelineStep
{
private readonly string _filePath;
private readonly string _newPath;
public MoveFileStep(string filePath, string newPath)
{
_filePath = filePath;
_newPath = newPath;
}
public override void Execute(ProjectBuildContext context)
{
var fileToMove = context.Files.Find(x => x.Name == _filePath);
var newFileExist = context.Files.Any(x => x.Name == _newPath);
if (fileToMove == null || newFileExist)
{
return;
}
fileToMove.SetName(_newPath);
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveFileStep.cs

@ -12,7 +12,7 @@ public class RemoveFileStep : ProjectBuildPipelineStep
public override void Execute(ProjectBuildContext context) public override void Execute(ProjectBuildContext context)
{ {
var fileToRemove = context.Files.Find(x => x.Name.EndsWith(_filePath)); var fileToRemove = context.Files.Find(x => x.Name == _filePath);;
if (fileToRemove != null) if (fileToRemove != null)
{ {
context.Files.Remove(fileToRemove); context.Files.Remove(fileToRemove);

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs

@ -13,6 +13,8 @@ public class TemplateCodeDeleteStep : ProjectBuildPipelineStep
file.Name.EndsWith(".cshtml") || file.Name.EndsWith(".cshtml") ||
file.Name.EndsWith(".json") || file.Name.EndsWith(".json") ||
file.Name.EndsWith(".gitignore") || file.Name.EndsWith(".gitignore") ||
file.Name.EndsWith(".yml") ||
file.Name.EndsWith(".ps1") ||
file.Name.EndsWith(".html")) file.Name.EndsWith(".html"))
{ {
file.RemoveTemplateCode(context.Symbols); file.RemoveTemplateCode(context.Symbols);

40
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppNoLayersTemplateBase.cs

@ -81,19 +81,12 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
break; break;
} }
//ConfigureTenantSchema(context, steps);
//SwitchDatabaseProvider(context, steps);
//DeleteUnrelatedProjects(context, steps);
steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName/Migrations")); steps.Add(new RemoveFolderStep("/aspnet-core/MyCompanyName.MyProjectName/Migrations"));
//RemoveMigrations(context, steps);
//ConfigureTieredArchitecture(context, steps);
//ConfigurePublicWebSite(context, steps);
//RemoveUnnecessaryPorts(context, steps);
RandomizeSslPorts(context, steps); RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps); RandomizeStringEncryption(context, steps);
UpdateNuGetConfig(context, steps); UpdateNuGetConfig(context, steps);
ChangeConnectionString(context, steps); ChangeConnectionString(context, steps);
//CleanupFolderHierarchy(context, steps); ConfigureDockerFiles(context, steps);
if (context.BuildArgs.UiFramework != UiFramework.Angular) if (context.BuildArgs.UiFramework != UiFramework.Angular)
{ {
@ -102,4 +95,35 @@ public abstract class AppNoLayersTemplateBase : AppTemplateBase
return steps; return steps;
} }
protected void ConfigureDockerFiles(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
switch (context.BuildArgs.UiFramework)
{
case UiFramework.None:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.Server.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/dynamic-env.json"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Host.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
case UiFramework.Angular:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.Server.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Host.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
case UiFramework.BlazorServer:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Host.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/dynamic-env.json"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.Server.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
case UiFramework.NotSpecified:
case UiFramework.Mvc:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.Server.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Host.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/dynamic-env.json"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
}
}
} }

32
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs

@ -466,4 +466,36 @@ public abstract class AppTemplateBase : TemplateInfo
steps.Add(new MoveFolderStep("/aspnet-core/", "/")); steps.Add(new MoveFolderStep("/aspnet-core/", "/"));
} }
} }
private void ConfigureDockerFiles(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
switch (context.BuildArgs.UiFramework)
{
case UiFramework.None:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/dynamic-env.json"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Angular.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
case UiFramework.Angular:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Angular.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
case UiFramework.Blazor:
case UiFramework.BlazorServer:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Angular.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/dynamic-env.json"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
case UiFramework.NotSpecified:
case UiFramework.Mvc:
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Blazor.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/docker-compose.Angular.yml"));
steps.Add(new RemoveFileStep("/aspnet-core/etc/docker/dynamic-env.json"));
steps.Add(new MoveFileStep("/aspnet-core/etc/docker/docker-compose.Mvc.yml", "/aspnet-core/etc/docker/docker-compose.yml"));
break;
}
}
} }

25
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs

@ -1,5 +1,6 @@
using System; using System;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Dtos;
@ -16,6 +17,18 @@ public abstract class ExtensibleAuditedEntityDto<TPrimaryKey> : ExtensibleCreati
/// <inheritdoc /> /// <inheritdoc />
public Guid? LastModifierId { get; set; } public Guid? LastModifierId { get; set; }
protected ExtensibleAuditedEntityDto()
: this(true)
{
}
protected ExtensibleAuditedEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }
/// <summary> /// <summary>
@ -30,4 +43,16 @@ public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEnti
/// <inheritdoc /> /// <inheritdoc />
public Guid? LastModifierId { get; set; } public Guid? LastModifierId { get; set; }
protected ExtensibleAuditedEntityDto()
: this(true)
{
}
protected ExtensibleAuditedEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }

25
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs

@ -1,5 +1,6 @@
using System; using System;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Dtos;
@ -18,6 +19,18 @@ public abstract class ExtensibleAuditedEntityWithUserDto<TPrimaryKey, TUserDto>
/// <inheritdoc /> /// <inheritdoc />
public TUserDto LastModifier { get; set; } public TUserDto LastModifier { get; set; }
protected ExtensibleAuditedEntityWithUserDto()
: this(true)
{
}
protected ExtensibleAuditedEntityWithUserDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }
/// <summary> /// <summary>
@ -35,4 +48,16 @@ public abstract class ExtensibleAuditedEntityWithUserDto<TUserDto> : ExtensibleA
/// <inheritdoc /> /// <inheritdoc />
public TUserDto LastModifier { get; set; } public TUserDto LastModifier { get; set; }
protected ExtensibleAuditedEntityWithUserDto()
: this(true)
{
}
protected ExtensibleAuditedEntityWithUserDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }

25
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs

@ -1,5 +1,6 @@
using System; using System;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Dtos;
@ -16,6 +17,18 @@ public abstract class ExtensibleCreationAuditedEntityDto<TPrimaryKey> : Extensib
/// <inheritdoc /> /// <inheritdoc />
public Guid? CreatorId { get; set; } public Guid? CreatorId { get; set; }
protected ExtensibleCreationAuditedEntityDto()
: this(true)
{
}
protected ExtensibleCreationAuditedEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }
/// <summary> /// <summary>
@ -30,4 +43,16 @@ public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto,
/// <inheritdoc /> /// <inheritdoc />
public Guid? CreatorId { get; set; } public Guid? CreatorId { get; set; }
protected ExtensibleCreationAuditedEntityDto()
: this(true)
{
}
protected ExtensibleCreationAuditedEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }

25
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs

@ -1,5 +1,6 @@
using System; using System;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Dtos;
@ -14,6 +15,18 @@ namespace Volo.Abp.Application.Dtos;
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, ICreationAuditedObject<TUserDto> public abstract class ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, ICreationAuditedObject<TUserDto>
{ {
public TUserDto Creator { get; set; } public TUserDto Creator { get; set; }
protected ExtensibleCreationAuditedEntityWithUserDto()
: this(true)
{
}
protected ExtensibleCreationAuditedEntityWithUserDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }
/// <summary> /// <summary>
@ -27,4 +40,16 @@ public abstract class ExtensibleCreationAuditedEntityWithUserDto<TUserDto> : Ext
ICreationAuditedObject<TUserDto> ICreationAuditedObject<TUserDto>
{ {
public TUserDto Creator { get; set; } public TUserDto Creator { get; set; }
protected ExtensibleCreationAuditedEntityWithUserDto()
: this(true)
{
}
protected ExtensibleCreationAuditedEntityWithUserDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }

24
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs

@ -11,6 +11,18 @@ public abstract class ExtensibleEntityDto<TKey> : ExtensibleObject, IEntityDto<T
/// </summary> /// </summary>
public TKey Id { get; set; } public TKey Id { get; set; }
protected ExtensibleEntityDto()
: this(true)
{
}
protected ExtensibleEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
public override string ToString() public override string ToString()
{ {
return $"[DTO: {GetType().Name}] Id = {Id}"; return $"[DTO: {GetType().Name}] Id = {Id}";
@ -20,6 +32,18 @@ public abstract class ExtensibleEntityDto<TKey> : ExtensibleObject, IEntityDto<T
[Serializable] [Serializable]
public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto
{ {
protected ExtensibleEntityDto()
: this(true)
{
}
protected ExtensibleEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
public override string ToString() public override string ToString()
{ {
return $"[DTO: {GetType().Name}]"; return $"[DTO: {GetType().Name}]";

25
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs

@ -1,5 +1,6 @@
using System; using System;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Dtos;
@ -19,6 +20,18 @@ public abstract class ExtensibleFullAuditedEntityDto<TPrimaryKey> : ExtensibleAu
/// <inheritdoc /> /// <inheritdoc />
public DateTime? DeletionTime { get; set; } public DateTime? DeletionTime { get; set; }
protected ExtensibleFullAuditedEntityDto()
: this(true)
{
}
protected ExtensibleFullAuditedEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }
/// <summary> /// <summary>
@ -36,4 +49,16 @@ public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDt
/// <inheritdoc /> /// <inheritdoc />
public DateTime? DeletionTime { get; set; } public DateTime? DeletionTime { get; set; }
protected ExtensibleFullAuditedEntityDto()
: this(true)
{
}
protected ExtensibleFullAuditedEntityDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }

25
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs

@ -1,5 +1,6 @@
using System; using System;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos; namespace Volo.Abp.Application.Dtos;
@ -21,6 +22,18 @@ public abstract class ExtensibleFullAuditedEntityWithUserDto<TPrimaryKey, TUserD
/// <inheritdoc /> /// <inheritdoc />
public TUserDto Deleter { get; set; } public TUserDto Deleter { get; set; }
protected ExtensibleFullAuditedEntityWithUserDto()
: this(true)
{
}
protected ExtensibleFullAuditedEntityWithUserDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }
/// <summary> /// <summary>
@ -41,4 +54,16 @@ public abstract class ExtensibleFullAuditedEntityWithUserDto<TUserDto> : Extensi
/// <inheritdoc /> /// <inheritdoc />
public TUserDto Deleter { get; set; } public TUserDto Deleter { get; set; }
protected ExtensibleFullAuditedEntityWithUserDto()
: this(true)
{
}
protected ExtensibleFullAuditedEntityWithUserDto(bool setDefaultsForExtraProperties)
: base(setDefaultsForExtraProperties)
{
}
} }

42
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs

@ -15,7 +15,7 @@ namespace Volo.Abp.Domain.Repositories;
public static class RepositoryExtensions public static class RepositoryExtensions
{ {
public static async Task EnsureCollectionLoadedAsync<TEntity, TKey, TProperty>( public async static Task EnsureCollectionLoadedAsync<TEntity, TKey, TProperty>(
this IBasicRepository<TEntity, TKey> repository, this IBasicRepository<TEntity, TKey> repository,
TEntity entity, TEntity entity,
Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression, Expression<Func<TEntity, IEnumerable<TProperty>>> propertyExpression,
@ -31,7 +31,7 @@ public static class RepositoryExtensions
} }
} }
public static async Task EnsurePropertyLoadedAsync<TEntity, TKey, TProperty>( public async static Task EnsurePropertyLoadedAsync<TEntity, TKey, TProperty>(
this IBasicRepository<TEntity, TKey> repository, this IBasicRepository<TEntity, TKey> repository,
TEntity entity, TEntity entity,
Expression<Func<TEntity, TProperty>> propertyExpression, Expression<Func<TEntity, TProperty>> propertyExpression,
@ -47,7 +47,33 @@ public static class RepositoryExtensions
} }
} }
public static async Task HardDeleteAsync<TEntity>( public async static Task EnsureExistsAsync<TEntity, TKey>(
this IRepository<TEntity, TKey> repository,
TKey id,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity<TKey>
{
if (!await repository.AnyAsync(x => x.Id.Equals(id), cancellationToken))
{
throw new EntityNotFoundException(typeof(TEntity), id);
}
}
public async static Task EnsureExistsAsync<TEntity, TKey>(
this IRepository<TEntity, TKey> repository,
Expression<Func<TEntity, bool>> expression,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity<TKey>
{
if (!await repository.AnyAsync(expression, cancellationToken))
{
throw new EntityNotFoundException(typeof(TEntity));
}
}
public async static Task HardDeleteAsync<TEntity>(
this IRepository<TEntity> repository, this IRepository<TEntity> repository,
Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, bool>> predicate,
bool autoSave = false, bool autoSave = false,
@ -71,7 +97,7 @@ public static class RepositoryExtensions
} }
} }
public static async Task HardDeleteAsync<TEntity>( public async static Task HardDeleteAsync<TEntity>(
this IBasicRepository<TEntity> repository, this IBasicRepository<TEntity> repository,
IEnumerable<TEntity> entities, IEnumerable<TEntity> entities,
bool autoSave = false, bool autoSave = false,
@ -95,7 +121,7 @@ public static class RepositoryExtensions
} }
} }
public static async Task HardDeleteAsync<TEntity>( public async static Task HardDeleteAsync<TEntity>(
this IBasicRepository<TEntity> repository, this IBasicRepository<TEntity> repository,
TEntity entity, TEntity entity,
bool autoSave = false, bool autoSave = false,
@ -139,7 +165,7 @@ public static class RepositoryExtensions
return unitOfWorkManagerAccessor.UnitOfWorkManager; return unitOfWorkManagerAccessor.UnitOfWorkManager;
} }
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>( private async static Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IRepository<TEntity> repository, IRepository<TEntity> repository,
Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, bool>> predicate,
bool autoSave, bool autoSave,
@ -155,7 +181,7 @@ public static class RepositoryExtensions
} }
} }
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>( private async static Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IBasicRepository<TEntity> repository, IBasicRepository<TEntity> repository,
IEnumerable<TEntity> entities, IEnumerable<TEntity> entities,
bool autoSave, bool autoSave,
@ -173,7 +199,7 @@ public static class RepositoryExtensions
await repository.DeleteManyAsync(entities, autoSave, cancellationToken); await repository.DeleteManyAsync(entities, autoSave, cancellationToken);
} }
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>( private async static Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IBasicRepository<TEntity> repository, IBasicRepository<TEntity> repository,
TEntity entity, TEntity entity,
bool autoSave, bool autoSave,

2
framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs

@ -13,7 +13,7 @@ public class PostgreSqlDbContextEventOutbox<TDbContext> : DbContextEventOutbox<T
} }
[UnitOfWork] [UnitOfWork]
public override async Task DeleteAsync(Guid id) public async override Task DeleteAsync(Guid id)
{ {
var dbContext = (IHasEventOutbox)await DbContextProvider.GetDbContextAsync(); var dbContext = (IHasEventOutbox)await DbContextProvider.GetDbContextAsync();
var tableName = dbContext.OutgoingEvents.EntityType.GetSchemaQualifiedTableName(); var tableName = dbContext.OutgoingEvents.EntityType.GetSchemaQualifiedTableName();

11
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs

@ -56,4 +56,15 @@ public class DbContextEventOutbox<TDbContext> : IDbContextEventOutbox<TDbContext
dbContext.Remove(outgoingEvent); dbContext.Remove(outgoingEvent);
} }
} }
[UnitOfWork]
public virtual async Task DeleteManyAsync(IEnumerable<Guid> ids)
{
var dbContext = (IHasEventOutbox)await DbContextProvider.GetDbContextAsync();
var outgoingEvents = await dbContext.OutgoingEvents.Where(x => ids.Contains(x.Id)).ToListAsync();
if (outgoingEvents.Any())
{
dbContext.RemoveRange(outgoingEvents);
}
}
} }

31
framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs

@ -71,6 +71,10 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
private async Task ProcessEventAsync(ServiceBusReceivedMessage message) private async Task ProcessEventAsync(ServiceBusReceivedMessage message)
{ {
var eventName = message.Subject; var eventName = message.Subject;
if (eventName == null)
{
return;
}
var eventType = _eventTypes.GetOrDefault(eventName); var eventType = _eventTypes.GetOrDefault(eventName);
if (eventType == null) if (eventType == null)
{ {
@ -87,12 +91,33 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
await TriggerHandlersAsync(eventType, eventData); await TriggerHandlersAsync(eventType, eventData);
} }
public override async Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{ {
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.Id); await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.Id);
} }
public override async Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
{
var outgoingEventArray = outgoingEvents.ToArray();
var publisher = await _publisherPool.GetAsync(
_options.TopicName,
_options.ConnectionName);
using var messageBatch = await publisher.CreateMessageBatchAsync();
foreach (var outgoingEvent in outgoingEventArray)
{
if (!messageBatch.TryAddMessage(new ServiceBusMessage(outgoingEvent.EventData) { Subject = outgoingEvent.EventName }))
{
throw new AbpException("The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number");
}
}
await publisher.SendMessagesAsync(messageBatch);
}
public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig)
{ {
var eventType = _eventTypes.GetOrDefault(incomingEvent.EventName); var eventType = _eventTypes.GetOrDefault(incomingEvent.EventName);
if (eventType == null) if (eventType == null)
@ -180,7 +205,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
.Locking(factories => factories.Clear()); .Locking(factories => factories.Clear());
} }
protected override async Task PublishToEventBusAsync(Type eventType, object eventData) protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{ {
await PublishAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData); await PublishAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData);
} }

67
framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs

@ -79,12 +79,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return; return;
} }
string messageId = null; var messageId = message.GetMessageId();
if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes))
{
messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes);
}
if (await AddToInboxAsync(messageId, eventName, eventType, message.Value)) if (await AddToInboxAsync(messageId, eventName, eventType, message.Value))
{ {
@ -164,7 +159,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); 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 PublishAsync( await PublishAsync(
eventType, eventType,
@ -198,7 +193,43 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
); );
} }
public override async Task ProcessFromInboxAsync( public override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
{
var producer = ProducerPool.Get();
var outgoingEventArray = outgoingEvents.ToArray();
producer.BeginTransaction();
try
{
foreach (var outgoingEvent in outgoingEventArray)
{
var messageId = outgoingEvent.Id.ToString("N");
var headers = new Headers
{
{ "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)}
};
producer.Produce(
AbpKafkaEventBusOptions.TopicName,
new Message<string, byte[]>
{
Key = outgoingEvent.EventName,
Value = outgoingEvent.EventData,
Headers = headers
});
}
producer.CommitTransaction();
}
catch (Exception e)
{
producer.AbortTransaction();
throw;
}
return Task.CompletedTask;
}
public async override Task ProcessFromInboxAsync(
IncomingEventInfo incomingEvent, IncomingEventInfo incomingEvent,
InboxConfig inboxConfig) InboxConfig inboxConfig)
{ {
@ -241,13 +272,29 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return PublishAsync(topicName, eventName, body, headers, headersArguments); return PublishAsync(topicName, eventName, body, headers, headersArguments);
} }
private async Task PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary<string, object> headersArguments) private Task<DeliveryResult<string, byte[]>> PublishAsync(
string topicName,
string eventName,
byte[] body,
Headers headers,
Dictionary<string, object> headersArguments)
{ {
var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName);
return PublishAsync(producer, topicName, eventName, body, headers, headersArguments);
}
private Task<DeliveryResult<string, byte[]>> PublishAsync(
IProducer<string, byte[]> producer,
string topicName,
string eventName,
byte[] body,
Headers headers,
Dictionary<string, object> headersArguments)
{
SetEventMessageHeaders(headers, headersArguments); SetEventMessageHeaders(headers, headersArguments);
await producer.ProduceAsync( return producer.ProduceAsync(
topicName, topicName,
new Message<string, byte[]> new Message<string, byte[]>
{ {

18
framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs

@ -0,0 +1,18 @@
using Confluent.Kafka;
namespace Volo.Abp.EventBus.Kafka;
public static class MessageExtensions
{
public static string GetMessageId<TKey, TValue>(this Message<TKey, TValue> message)
{
string messageId = null;
if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes))
{
messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes);
}
return messageId;
}
}

108
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs

@ -34,6 +34,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; }
protected IRabbitMqMessageConsumer Consumer { get; private set; } protected IRabbitMqMessageConsumer Consumer { get; private set; }
private bool _exchangeCreated;
public RabbitMqDistributedEventBus( public RabbitMqDistributedEventBus(
IOptions<AbpRabbitMqEventBusOptions> options, IOptions<AbpRabbitMqEventBusOptions> options,
IConnectionPool connectionPool, IConnectionPool connectionPool,
@ -180,7 +182,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); 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 PublishAsync(eventType, eventData, null); await PublishAsync(eventType, eventData, null);
} }
@ -197,7 +199,30 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id);
} }
public override async Task ProcessFromInboxAsync( public async override Task PublishManyFromOutboxAsync(
IEnumerable<OutgoingEventInfo> outgoingEvents,
OutboxConfig outboxConfig)
{
using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
{
var outgoingEventArray = outgoingEvents.ToArray();
channel.ConfirmSelect();
foreach (var outgoingEvent in outgoingEventArray)
{
await PublishAsync(
channel,
outgoingEvent.EventName,
outgoingEvent.EventData,
properties: null,
eventId: outgoingEvent.Id);
}
channel.WaitForConfirmsOrDie();
}
}
public async override Task ProcessFromInboxAsync(
IncomingEventInfo incomingEvent, IncomingEventInfo incomingEvent,
InboxConfig inboxConfig) InboxConfig inboxConfig)
{ {
@ -233,7 +258,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
return PublishAsync(eventName, body, properties, headersArguments); return PublishAsync(eventName, body, properties, headersArguments);
} }
protected Task PublishAsync( protected virtual Task PublishAsync(
string eventName, string eventName,
byte[] body, byte[] body,
IBasicProperties properties, IBasicProperties properties,
@ -242,37 +267,66 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
{ {
using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
{ {
channel.ExchangeDeclare( return PublishAsync(channel, eventName, body, properties, headersArguments, eventId);
AbpRabbitMqEventBusOptions.ExchangeName, }
AbpRabbitMqEventBusOptions.GetExchangeTypeOrDefault(), }
durable: true
);
if (properties == null)
{
properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
}
if (properties.MessageId.IsNullOrEmpty()) protected virtual Task PublishAsync(
{ IModel channel,
properties.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N"); string eventName,
} byte[] body,
IBasicProperties properties,
Dictionary<string, object> headersArguments = null,
Guid? eventId = null)
{
EnsureExchangeExists(channel);
SetEventMessageHeaders(properties, headersArguments); if (properties == null)
{
properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
}
channel.BasicPublish( if (properties.MessageId.IsNullOrEmpty())
exchange: AbpRabbitMqEventBusOptions.ExchangeName, {
routingKey: eventName, properties.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N");
mandatory: true,
basicProperties: properties,
body: body
);
} }
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish(
exchange: AbpRabbitMqEventBusOptions.ExchangeName,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body
);
return Task.CompletedTask; return Task.CompletedTask;
} }
private void EnsureExchangeExists(IModel channel)
{
if (_exchangeCreated)
{
return;
}
try
{
channel.ExchangeDeclarePassive(AbpRabbitMqEventBusOptions.ExchangeName);
}
catch (Exception)
{
channel.ExchangeDeclare(
AbpRabbitMqEventBusOptions.ExchangeName,
AbpRabbitMqEventBusOptions.GetExchangeTypeOrDefault(),
durable: true
);
}
_exchangeCreated = true;
}
private void SetEventMessageHeaders(IBasicProperties properties, Dictionary<string, object> headersArguments) private void SetEventMessageHeaders(IBasicProperties properties, Dictionary<string, object> headersArguments)
{ {
if (headersArguments == null) if (headersArguments == null)
@ -306,7 +360,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>(); var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in foreach (var handlerFactory in
HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{ {
handlerFactoryList.Add( handlerFactoryList.Add(
new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));

23
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Rebus.Bus; using Rebus.Bus;
using Rebus.Pipeline; using Rebus.Pipeline;
using Rebus.Transport;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Guids; using Volo.Abp.Guids;
@ -149,6 +150,11 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
} }
protected async override Task PublishToEventBusAsync(Type eventType, object eventData) protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishAsync(eventType, eventData);
}
protected virtual async Task PublishAsync(Type eventType, object eventData)
{ {
if (AbpRebusEventBusOptions.Publish != null) if (AbpRebusEventBusOptions.Publish != null)
{ {
@ -218,7 +224,22 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return PublishToEventBusAsync(eventType, eventData); return PublishToEventBusAsync(eventType, eventData);
} }
public override async Task ProcessFromInboxAsync( public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
{
var outgoingEventArray = outgoingEvents.ToArray();
using (var scope = new RebusTransactionScope())
{
foreach (var outgoingEvent in outgoingEventArray)
{
await PublishFromOutboxAsync(outgoingEvent, outboxConfig);
}
await scope.CompleteAsync();
}
}
public async override Task ProcessFromInboxAsync(
IncomingEventInfo incomingEvent, IncomingEventInfo incomingEvent,
InboxConfig inboxConfig) InboxConfig inboxConfig)
{ {

6
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs

@ -34,6 +34,11 @@ public class AbpEventBusBoxesOptions
/// Default: 2 hours /// Default: 2 hours
/// </summary> /// </summary>
public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; } public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; }
/// <summary>
/// Default: true
/// </summary>
public bool BatchPublishOutboxEvents { get; set; }
public AbpEventBusBoxesOptions() public AbpEventBusBoxesOptions()
{ {
@ -43,5 +48,6 @@ public class AbpEventBusBoxesOptions
PeriodTimeSpan = TimeSpan.FromSeconds(2); PeriodTimeSpan = TimeSpan.FromSeconds(2);
DistributedLockWaitDuration = TimeSpan.FromSeconds(15); DistributedLockWaitDuration = TimeSpan.FromSeconds(15);
WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2); WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2);
BatchPublishOutboxEvents = true;
} }
} }

11
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs

@ -85,6 +85,11 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
OutboxConfig outboxConfig OutboxConfig outboxConfig
); );
public abstract Task PublishManyFromOutboxAsync(
IEnumerable<OutgoingEventInfo> outgoingEvents,
OutboxConfig outboxConfig
);
public abstract Task ProcessFromInboxAsync( public abstract Task ProcessFromInboxAsync(
IncomingEventInfo incomingEvent, IncomingEventInfo incomingEvent,
InboxConfig inboxConfig); InboxConfig inboxConfig);
@ -101,7 +106,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
{ {
if (outboxConfig.Selector == null || outboxConfig.Selector(eventType)) if (outboxConfig.Selector == null || outboxConfig.Selector(eventType))
{ {
var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); var eventOutbox =
(IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType);
var eventName = EventNameAttribute.GetNameOrDefault(eventType); var eventName = EventNameAttribute.GetNameOrDefault(eventType);
await eventOutbox.EnqueueAsync( await eventOutbox.EnqueueAsync(
new OutgoingEventInfo( new OutgoingEventInfo(
@ -135,7 +141,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
{ {
if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType)) if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType))
{ {
var eventInbox = (IEventInbox)scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); var eventInbox =
(IEventInbox)scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType);
if (!messageId.IsNullOrEmpty()) if (!messageId.IsNullOrEmpty())
{ {

2
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs

@ -12,4 +12,6 @@ public interface IEventOutbox
Task<List<OutgoingEventInfo>> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); Task<List<OutgoingEventInfo>> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default);
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
Task DeleteManyAsync(IEnumerable<Guid> ids);
} }

6
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Volo.Abp.EventBus.Distributed; namespace Volo.Abp.EventBus.Distributed;
@ -9,6 +10,11 @@ public interface ISupportsEventBoxes
OutboxConfig outboxConfig OutboxConfig outboxConfig
); );
Task PublishManyFromOutboxAsync(
IEnumerable<OutgoingEventInfo> outgoingEvents,
OutboxConfig outboxConfig
);
Task ProcessFromInboxAsync( Task ProcessFromInboxAsync(
IncomingEventInfo incomingEvent, IncomingEventInfo incomingEvent,
InboxConfig inboxConfig InboxConfig inboxConfig

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save