Browse Source

Merge branch 'dev' into auto-merge/rel-7-3/2019

pull/16906/head
Engincan VESKE 3 years ago
committed by GitHub
parent
commit
48d27a197f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .github/scripts/update_versions.py
  2. 3
      Directory.Build.props
  3. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  4. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  5. 45
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  6. 6
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
  7. 2
      common.props
  8. 267
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md
  9. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png
  10. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png
  11. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png
  12. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png
  13. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png
  14. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png
  15. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png
  16. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png
  17. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png
  18. BIN
      docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png
  19. 4
      docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md
  20. 8
      docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md
  21. 29
      framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs
  22. 28
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs
  23. 51
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs
  24. 18
      framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs
  25. 7
      framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs
  26. 2
      framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprSerializer.cs
  27. 5
      framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/Utf8JsonDaprSerializer.cs
  28. 12
      framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs
  29. 12
      framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs
  30. 9
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs
  31. 9
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs
  32. 318
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/DatabaseMigrationEventHandlerBase.cs
  33. 145
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs
  34. 11
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs
  35. 13
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs
  36. 6
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/EventBusConsts.cs
  37. 55
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs
  38. 23
      framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/AbpDaprEventData.cs
  39. 75
      framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs
  40. 82
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs
  41. 12
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs
  42. 88
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  43. 62
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs
  44. 55
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs
  45. 9
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs
  46. 9
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs
  47. 2
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs
  48. 3
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs
  49. 14
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs
  50. 14
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs
  51. 13
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs
  52. 29
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs
  53. 83
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs
  54. 6
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs
  55. 18
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs
  56. 81
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs
  57. 4
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs
  58. 11
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs
  59. 8
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs
  60. 56
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs
  61. 18
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs
  62. 5
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs
  63. 2
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs
  64. 2
      framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs
  65. 20
      framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs
  66. 17
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProviderController.cs
  67. 48
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProvider_Tests.cs
  68. 33
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/CorrelationIdProvider/CorrelationIdProvider_Tests.cs
  69. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs
  70. 19
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateContentFileProvider_Tests.cs
  71. 19
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs
  72. 17
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs
  73. 2
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs
  74. 29
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider_Tests.cs
  75. 6
      framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs
  76. 6
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj
  77. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo.Abp.OpenIddict.Domain.Shared.csproj
  78. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.csproj
  79. 2
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html
  80. 4
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html
  81. 2
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html
  82. 2
      npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.html
  83. 4
      npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts
  84. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Data/MyProjectNameEFCoreDbSchemaMigrator.cs
  85. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Data/MyProjectNameEFCoreDbSchemaMigrator.cs
  86. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Data/MyProjectNameEFCoreDbSchemaMigrator.cs
  87. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Data/MyProjectNameEFCoreDbSchemaMigrator.cs
  88. 2
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreMyProjectNameDbSchemaMigrator.cs

3
.github/scripts/update_versions.py

@ -51,5 +51,4 @@ def create_pr():
if __name__ == "__main__":
should_create_pr = update_latest_versions()
if should_create_pr:
create_pr()
create_pr()

3
Directory.Build.props

@ -42,6 +42,9 @@
<IsTestProject Condition="$(MSBuildProjectFullPath.Contains('test')) and ($(MSBuildProjectName.EndsWith('.Tests')) or $(MSBuildProjectName.EndsWith('.TestBase')))">true</IsTestProject>
<!-- OpenIddict https://www.nuget.org/packages/OpenIddict.Core -->
<OpenIddictPackageVersion>4.5.0</OpenIddictPackageVersion>
</PropertyGroup>
<ItemGroup>

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

@ -463,6 +463,7 @@
"DiscountAmount": "Discount Amount",
"FullChangeHistory": "Full Change History",
"Permission:RefreshReleaseLogs": "Refresh Release Logs",
"ReleaseLogs": "Release Logs"
"ReleaseLogs": "Release Logs",
"AuthorUserName": "Author User Name"
}
}

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

@ -211,6 +211,10 @@
"RecentActivities": "Recent Activities",
"SpringCampaign": "Welcome <br>Spring Sale!",
"SpringCampaign2": "<span>Limited <br> Time Offer!</span>",
"AboutUs": "About Us"
"AboutUs": "About Us",
"HowItWorks": "How it works?",
"ReleaseNotes": "Release Notes",
"DetailedChangeNotes" : "Detailed Change Notes",
"SeeTrainings": "See Trainings"
}
}

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

@ -844,7 +844,7 @@
"BlazoriseSupportExplanation5": "You can post your questions on the support website and generate a product token for your application.",
"AbpLiveTrainingPackages": "ABP Live Training Packages",
"Releases": "Releases",
"ReleasesDescription": "ABP Commercial releases and their changelogs.",
"ReleasesDescription": "Release logs of ABP Commercial.",
"ReleaseDate": "Release Date",
"Labels": "Labels",
"PreRelease": "Pre-release",
@ -853,6 +853,47 @@
"Bug": "Bug",
"Feature": "Feature",
"AllUIs": "All UI's",
"MVC": "MVC"
"MVC": "MVC",
"HowItWorks_Page_Title": "How it works?",
"HowItWorks_Page_Description": "ABP Framework extends the .NET platform. So, anything you can do with a plain .NET solution is already possible with the ABP Framework. That makes it easy to get started with a low learning curve.",
"HowItWorks_Description1": "ABP Framework extends the .NET platform. So, anything you can do with a plain .NET solution is already possible with the ABP Framework. That makes it easy to get started with a low learning curve.",
"HowItWorks_Description2": "Once you start learning and using the ABP Framework features, developing your software will be much more enjoyable than ever.",
"HowItWorks_Description3": "This page basically explains how you use the ABP.IO Platform as a .NET developer.",
"CreateANewSolution": "Create a New .NET Solution",
"CreateANewSolution_Description1": "Everything starts by creating a new ABP integrated .NET solution.",
"StartWithStartupTemplates": "Start one of the pre-built <a href=\"/startup-templates\">startup solution templates</a>",
"SimpleMonolithApplicationTemplate": "Simple monolith application template",
"LayeredApplicationTemplate": "Layered application template",
"MicroserviceSolutionTemplate": "Microservice solution template",
"CreateEmptySolutionAndUseAbp": "Or create a new empty .NET solution and install ABP NuGet & NPM <a href=\"https://abp.io/packages\" target=\"_blank\">packages</a> yourself.",
"CreatingSolutionWithMultipleOptions": "There are multiple User Interface and Database options while creating a new solution.",
"UIFrameworkOptions": "UI Framework Options",
"DotnetSolutionWithoutDependency": "Now, you have a regular .NET solution in your local computer that has no dependency to a cloud platform or external service.",
"CheckTheDocumentForDetails": "You can check the <a href=\"{0}\" target=\"_blank\">{1}</a> document for details.",
"UIAndDatabaseIndependent": "ABP can work with any UI and any database provider supported by .NET. \n However, these UI and database providers are pre-integrated and well documented.",
"InstallAbpModules": "Install ABP Modules",
"DevelopYourSolution": "Develop Your Solution",
"DeployAnywhere": "Deploy Anywhere",
"InstallAbpModule_Description1": "ABP is a modular application development framework. Startup solution templates already come with the essential modules installed. \n But there are more application modules you may want to use in your solution.",
"InstallAbpModule_Description2": "Every module consists of a few NuGet and NPM packages and has an installation document. <a href=\"/tools/suite\">ABP Suite</a> does most of the work automatically, then you manually configure or fine tune the module based on its documentation.",
"DevelopYourSolution_Description1": "ABP’s infrastructure makes you <b class=\"text-white\">focus on your own business code</b> by automating the repetitive work and providing pre-built infrastructure and application <a href=\"https://abp.io/features\" target=\"_blank\">features</a>.",
"DevelopYourSolution_Description2": "In the following code block, you can see how the ABP Framework seamlessly integrates into your code and automates the repetitive tasks for you.",
"DevelopYourSolution_Description3": "Even in this short code block, ABP does a lot of things for you.",
"DevelopYourSolution_Description4": "It provides base classes to apply conventions, like \n <a href=\"https://docs.abp.io/en/abp/latest/Dependency-Injection\" target=\"_blank\">dependency injection</a>. Generic \n <a href=\"https://docs.abp.io/en/abp/latest/Repositories\" target=\"_blank\">repository</a> services provide a convenient \n way to interact with the database. Declarative \n <a href=\"https://docs.abp.io/en/abp/latest/Authorization\" target=\"_blank\">authorization</a> works with a fine-tuned permission system.",
"DevelopYourSolution_Description5": "ABP completely automates \n <a href=\"https://docs.abp.io/en/abp/latest/Unit-Of-Work\" target=\"_blank\">unit of work</a> (for database connection and transaction management), \n <a href=\"https://docs.abp.io/en/abp/latest/Exception-Handling\" target=\"_blank\">exception handling</a>, \n <a href=\"https://docs.abp.io/en/abp/latest/Validation\" target=\"_blank\">validation</a>\n and <a href=\"https://docs.abp.io/en/abp/latest/Audit-Logging\" target=\"_blank\">audit logging</a>. It provides many more building blocks to simplify your daily development tasks and focus on your own code while creating production ready \n applications.",
"DevelopYourSolution_Description6": "You can imagine how much that code block can be long and complicated if you would do it all manually.",
"SuiteCrudGenerationInFewSeconds": "In addition to hand coding your solution, you can create fully working advanced CRUD pages in a few minutes using the ABP Suite tooling. It generates the code into your solution, so you can fine tune it based on your custom requirements.",
"DeployAnywhere_Description1": "At the end of the day, you have a pure .NET solution. You can deploy your solution to your own server, to a cloud platform, kubernetes or anywhere you want. You can deploy to as many servers as you want. ABP is a deployment environment agnostic tool.",
"ExpertiseAbpFramework": "Expertise the ABP Framework",
"ExpertiseAbpFramework_Description1": "Want to go beyond basics and get expertise with the ABP.IO Platform?",
"FreeDownload": "Free Download",
"Read": "Read",
"HavingTrouble": "Having Trouble?",
"HavingTrouble_Description1": "Do you have problems with developing your solution? We are here! Use the ABP Support platform \n or send an email to get help directly from the Core ABP Framework team members.",
"WeAreHereToHelp_Description1": "You can browse our help topics or search in the frequently asked questions, \n or you can ask us a question by using the contact form.",
"OtherModules": "Other Modules",
"OtherModules_Description1": "Account, Audit Logging, Chat , CMS Kit,File Management , Forms, GDPR, Identity, Language Management, Payment , Saas and more...",
"HowItWorks_DatabaseProviderOptions": "Database provider options",
"SeeFAQ" : "See FAQ"
}
}

6
abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json

@ -129,8 +129,8 @@
"TellUsWhatYouNeed": "Bize neye ihtiyacın olduğunu söyle.",
"YourMessage": "Mesajın",
"YourFullName": "Tam adınız",
"EmailField": "E",
"YourEmailAddress": "E",
"EmailField": "E-posta Adresi",
"YourEmailAddress": "E-posta adresiniz",
"HowMayWeHelpYou": "Size nasıl yardımcı olabiliriz?",
"SendMessage": "Mesaj gönder",
"Success": "Başarı",
@ -808,4 +808,4 @@
"BlazoriseLicense": "Blazorise lisansı satın almamız gerekiyor mu?",
"BlazoriseLicenseExplanation": "Volosoft ve Megabit arasında bir anlaşmamız var, bu anlaşma ile Blazorise lisansı ABP Ticari ürünleri ile birlikte geliyor, bu nedenle müşterilerimizin ekstra bir Blazorise lisansı satın almasına gerek kalmıyor."
}
}
}

2
common.props

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

267
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/POST.md

@ -0,0 +1,267 @@
# ABP.IO Platform 7.3 RC Has Been Released
Today, we are happy to release the [ABP Framework](https://abp.io/) and [ABP Commercial](https://commercial.abp.io/) version **7.3 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version.
Try this version and provide feedback for a more stable version of ABP v7.3! Thanks to all of you.
## Get Started with the 7.3 RC
Follow the steps below to try version 7.3.0 RC today:
1) **Upgrade** the ABP CLI to version `7.3.0-rc.1` using a command line terminal:
````bash
dotnet tool update Volo.Abp.Cli -g --version 7.3.0-rc.1
````
**or install** it if you haven't before:
````bash
dotnet tool install Volo.Abp.Cli -g --version 7.3.0-rc.1
````
2) Create a **new application** with the `--preview` option:
````bash
abp new BookStore --preview
````
See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options.
> You can also use the [Get Started](https://abp.io/get-started) page to generate a CLI command to create a new application.
You can use any IDE that supports .NET 7.x, like [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).
## Migration Guides
There are a few breaking changes in this version that may affect your application.
Please see the following migration documents, if you are upgrading from v7.2:
* [ABP Framework 7.2 to 7.3 Migration Guide](https://docs.abp.io/en/abp/7.3/Migration-Guides/Abp-7_3)
> If you are using the CMS Kit or CMS Kit Pro module, please don't forget to create a new migration and apply it to your database.
## What's New with ABP Framework 7.3?
In this section, I will introduce some major features released in this version. Here is a brief list of the titles that will be explained in the next sections:
* Introducing the Volo.Abp.Imaging packages
* ABP CLI: switch-to-local command
* Monitoring Distributed Events
* Ordering of the Local Event Handlers
* Nonce attribute support for Content Security Policy (CSP)
* Other News
### Introducing the Volo.Abp.Imaging packages
ABP Framework provides some packages to compress and resize images. Currently, there are four official packages:
* `Volo.Abp.Imaging.Abstractions`: Provides common services for compression and resizing purposes.
* `Volo.Abp.Imaging.AspNetCore`: Provides some attributes for controller actions that can automatically compress and/or resize uploaded files.
* `Volo.Abp.Imaging.ImageSharp`: Implements the image compression & resize operations using the [ImageSharp](https://github.com/SixLabors/ImageSharp) library.
* `Volo.Abp.Imaging.MagickNet`: Implements the image compression & resize operations using the [Magick.NET](https://github.com/dlemstra/Magick.NET) library.
You can use one of these official providers (`ImageSharp` or `Magick.NET`) or implement your own image resizer/compressor contributor and use it in your application.
> See the [Image Manipulation](https://docs.abp.io/en/abp/7.3/Image-Manipulation) documentation to learn more and see the required configurations.
### ABP CLI: switch-to-local command
In this version, ABP CLI introduces a new CLI command: **"switch-to-local"**. The `switch-to-local` command changes all NuGet package references on a solution to local project references for all the `.csproj` files in the specified folder (and all its subfolders with any depth).
**Usage:**
```bash
abp switch-to-local --paths "C:\Github\abp"
```
### Monitoring Distributed Events
ABP Framework allows you to stay informed when your application **receives** or **sends** a distributed event. This enables you to track the event flow within your application and take appropriate actions based on the received or sent distributed events.
You just need to subscribe to one of the `DistributedEventReceived` or `DistributedEventSent` events and take additional actions according to your cases.
**Example: Get informed when your application sends an event to the distributed event bus**
```csharp
public class DistributedEventSentHandler : ILocalEventHandler<DistributedEventSent>, ITransientDependency
{
public async Task HandleEventAsync(DistributedEventSent eventData)
{
// TODO: IMPLEMENT YOUR LOGIC...
}
}
```
> See the documentation to learn more: [https://docs.abp.io/en/abp/7.3/Distributed-Event-Bus](https://docs.abp.io/en/abp/7.3/Distributed-Event-Bus)
### Ordering of the Local Event Handlers
In this version, ABP Framework introduces the `LocalEventHandlerOrder` attribute, which can be used to set the execution order for the event handlers. This can be helpful if you want to handle your local event handlers in a specific order.
**Example:**
```csharp
[LocalEventHandlerOrder(-1)]
public class MyHandler
: ILocalEventHandler<StockCountChangedEvent>,
ITransientDependency
{
public async Task HandleEventAsync(StockCountChangedEvent eventData)
{
//TODO: your implementation
}
}
```
By default, all event handlers have an order value of 0. Thus, if you want to take certain event handlers to be executed before other event handlers, you can set the order value as a negative value.
> See the documentation to learn more: [https://docs.abp.io/en/abp/7.3/Local-Event-Bus](https://docs.abp.io/en/abp/7.3/Local-Event-Bus)
### Nonce attribute support for Content Security Policy (CSP)
ABP Framework supports adding unique value to nonce attribute for script tags which can be used by Content Security Policy to determine whether or not a given fetch will be allowed to proceed for a given element. In other words, it provides a mechanism to execute only correct script tags with the correct nonce value.
This feature is disabled by default. You can enable it by setting the *UseContentSecurityPolicyScriptNonce* property of the `AbpSecurityHeadersOptions` class to **true**:
```csharp
Configure<AbpSecurityHeadersOptions>(options =>
{
//adding script-src nonce
options.UseContentSecurityPolicyScriptNonce = true; //false by default
});
```
> See the [Security Headers](https://docs.abp.io/en/abp/7.3/UI/AspNetCore/Security-Headers) documentation for more information.
### Other News
* Upgraded the [Blazorise](https://blazorise.com/) library to v1.2.3 for Blazor UI. After the upgrade, ensure that all Blazorise-related packages are using v1.2.3 in your application.
* Module Entity Extension support has been added for the CMS Kit module. See [#16572](https://github.com/abpframework/abp/issues/16572) for more information.
If you want to see more details, you can check [the release on GitHub](https://github.com/abpframework/abp/releases/tag/5.3.0-rc.1), which contains a list of all the issues and pull requests were closed with this version.
## What's New with ABP Commercial 7.3?
We've also worked on [ABP Commercial](https://commercial.abp.io/) to align the features and changes made in the ABP Framework. The following sections introduce a few new features coming with ABP Commercial 7.3.
### Account Module - Using Authenticator App for Two-Factor Authentication
In this version, ABP Commercial provides a new **Two-Factor Authentication (2FA) provider** that allows you to log in to an application by scanning a QR Code with an Authenticator App, such as Microsoft Authenticator or Google Authenticator.
You need to apply the following actions to configure an Authenticator and then you are free to log in by using the Authenticator App:
**Step 1 - Enable Two Factor Authentication and Scan the QR Code:**
![](./two-factor-auth-1.png)
**Step 2 - Verify the QR Code with an authenticator app:**
![](./two-factor-auth-2.png)
**Step 3 - Save the recovery codes for later use in case of not being able to login by verifying the QR code:**
![](./two-factor-auth-3.png)
You can disable the two-factor authentication and reset the Authenticator App anytime you want, just by disabling the two-factor authentication or resetting the authenticator:
![](./reset-authenticator.png)
### Upgrade Blazorise to v1.2.3
Upgraded the [Blazorise](https://blazorise.com/) library to v1.2.3 for Blazor UI. If you are upgrading your project to v7.3.0, please ensure that all the Blazorise-related packages are using v1.2.3 in your application. Otherwise, you might get errors due to incompatible versions.
### CMS Kit: Module Entity Extensions
Module entity extension system is a high-level extension system that allows you to define new properties for existing entities of the dependent modules. ABP Framework and ABP Commercial use this system to allow developers to extend entities in different modules.
In this version, Module Entity Extension support has been added for the CMS Kit Pro module.
You can open the `YourProjectNameModuleExtensionConfigurator` class inside the `Domain.Shared` project of your solution and change the `ConfigureExtraProperties` method as shown below to add a new property to the `Poll` entity of the [CMS Kit Pro module](https://docs.abp.io/en/commercial/latest/modules/cms-kit/index):
```csharp
public static void ConfigureExtraProperties()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance.Modules()
.ConfigureCmsKitPro(cmsKitPro =>
{
cmsKitPro.ConfigurePoll(poll =>
{
poll.AddOrUpdateProperty<string>(
"<property-name>",
property =>
{
//configuration for this property
}
)
});
});
});
}
```
> See the [Module Entity Extensions documentation](https://docs.abp.io/en/abp/latest/Module-Entity-Extensions) to learn more.
### LeptonX Account Layout
In this version, Account Layout has been re-designed for LeptonX Theme. You can see the new account layout in the following figure:
![](leptonx-account-layout.png)
> To use this new account layout, ensure that your LeptonX Theme package versions are v2.3+.
## Community News
### ABP Community Talks 2023.4: Angular 16 and ABP v7.3
![](./community-talks-2023-4.png)
In this episode, the core ABP team talked about what's new with ABP v7.3 and Angular 16. You can watch the event from [here](https://www.youtube.com/watch?v=lq6u4vQURcI).
### ABP .NET Conference 2023
![](./abp-conf.png)
We organized ABP .NET Conference 2023 on May 2023 and we are happy to share the success of the conference, which captivated overwhelmingly interested live viewers from all over the world. 13 great line up of speakers which includes .NET experts and Microsoft MVPs delivered captivating talks that resonated with the audiences. Each of the talks attracted a great amount of interest and a lot of questions, sparking curiosity in the attendees.
Thanks to all speakers and attendees for joining our event.
> We shared our takeaways in a blog post, which you can read at [https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up](https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up).
### Volosoft Attendeed & Sponsored Devnot .NET Conference 2023
![](devnot-conference.png)
We are thrilled to announce that the Volosoft Company proudly attended as one of the Gold Sponsors at the Devnot .NET Conference 2023! We are happy to join and be a sponsor of events and contribute to the software society, empowering developers and driving innovation with the .NET community.
![](devnot-talk.png)
Co-Founder of [Volosoft](https://volosoft.com/) and Lead Developer of the ABP Framework, [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan) gave a word about "Dealing with Concurrency and Multi Threading in .NET" at this event.
> You can check [this blog post](https://volosoft.com/blog/Reflecting-on-Devnot-Dotnet-Conference-2023) if you want to learn more about the Devnot .NET Conference 2023.
### New ABP Community Posts
There are exciting articles contributed by the ABP community as always. I will highlight some of them here:
* [Authority Delegation in ABP Commercial](https://community.abp.io/posts/authority-delegation-in-abp-commercial-3wtljpp0) by [Liang Shiwei](https://github.com/realLiangshiwei)
* [What's new in Angular 16? New Features and Updates](https://community.abp.io/posts/whats-new-in-angular-16-new-features-and-updates-s1izi9br) by [Masum Ulu](https://twitter.com/masumulu)
* [Kubernetes Integrated Microservice Development with ABP Studio](https://community.abp.io/videos/kubernetes-integrated-microservice-development-with-abp-studio-oix9zkp8) by [Halil Ibrahim Kalkan](https://twitter.com/hibrahimkalkan)
Thanks to the ABP Community for all the content they have published. You can also [post your ABP-related (text or video) content](https://community.abp.io/articles/submit) to the ABP Community.
### New ABP Blog Posts
There are also some exciting blog posts written by the ABP team. You can see the following list for some of those articles:
* [ABP .NET Conference 2023 Wrap Up](https://blog.abp.io/abp/ABP-.NET-Conference-2023-Wrap-Up) by [Bige Beşikçi](https://twitter.com/bigedediki)
* [Meet Volosoft at the Devnot .NET Conference 2023!](https://volosoft.com/blog/Meet-Volosoft-at-the-Devnot-.NET-Conference-2023) by [Roo Xu](https://github.com/Roo1227)
## Conclusion
This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://docs.abp.io/en/abp/7.3/Road-Map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v7.3 RC and provide feedback to help us release a more stable version.
Thanks for being a part of this community!

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/abp-conf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/community-talks-2023-4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/cover-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-conference.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/devnot-talk.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/leptonx-account-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/reset-authenticator.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/en/Blog-Posts/2023-06-05 v7_3_Preview/two-factor-auth-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

4
docs/en/Community-Articles/2023-06-05-Kubernetes-Integration-Abp-Studio/POST.md

@ -163,12 +163,10 @@ The solution runner is a great way of running multiple services locally. However
### How ABP Studio Kubernetes Tunnel works
I am sure that you want to see it in action, but before that, let me explain how the solution works. No problem if you can not understand it completely. It is enough to see the big picture:
I am sure that you want to see it in action, but before that, let me explain how the solution works.
![abp-studio-kubernetes-tunnel-how-it-works](06-abp-studio-kubernetes-tunnel-how-it-works.png)
Let's explain how it works:
* **Kubernetes cluster** is shown on the right side and your **local development machine** is shown on the left side.
* As you know, when a **user requests a web page** from your web application, the request is accepted by an **Ingress Controller** inside your Kubernetes cluster.
* The Ingress controller forwards the request to your **web application**, which then uses an **API gateway** to consume your microservices.

8
docs/en/Migration-Guides/docs/en/Migration-Guides/Abp-7_4.md

@ -0,0 +1,8 @@
# ABP Version 7.4 Migration Guide
This document is a guide for upgrading ABP v7.3 solutions to ABP v7.4. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application.
## TemplateDefinition
The `LocalizationResource(Type)` of `TemplateDefinition` class is changed to `LocalizationResourceName(string)`.

29
framework/src/Volo.Abp.AspNetCore.Mvc.Dapr.EventBus/Volo/Abp/AspNetCore/Mvc/Dapr/EventBus/Controllers/AbpAspNetCoreMvcDaprEventsController.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
@ -21,7 +22,6 @@ public class AbpAspNetCoreMvcDaprEventsController : AbpController
var daprSerializer = HttpContext.RequestServices.GetRequiredService<IDaprSerializer>();
var body = (await JsonDocument.ParseAsync(HttpContext.Request.Body));
var id = body.RootElement.GetProperty("id").GetString();
var pubSubName = body.RootElement.GetProperty("pubsubname").GetString();
var topic = body.RootElement.GetProperty("topic").GetString();
var data = body.RootElement.GetProperty("data").GetRawText();
@ -32,8 +32,31 @@ public class AbpAspNetCoreMvcDaprEventsController : AbpController
}
var distributedEventBus = HttpContext.RequestServices.GetRequiredService<DaprDistributedEventBus>();
var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic));
await distributedEventBus.TriggerHandlersAsync(id, distributedEventBus.GetEventType(topic), eventData);
if (IsAbpDaprEventData(data))
{
var daprEventData = daprSerializer.Deserialize(data, typeof(AbpDaprEventData)).As<AbpDaprEventData>();
var eventData = daprSerializer.Deserialize(daprEventData.JsonData, distributedEventBus.GetEventType(daprEventData.Topic));
await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(daprEventData.Topic), eventData, daprEventData.MessageId, daprEventData.CorrelationId);
}
else
{
var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic));
await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic), eventData);
}
return Ok();
}
protected virtual bool IsAbpDaprEventData(string data)
{
var document = JsonDocument.Parse(data);
var objects = document.RootElement.EnumerateObject().ToList();
return objects.Count == 5 &&
objects.Any(x => x.Name.Equals("PubSubName", StringComparison.CurrentCultureIgnoreCase)) &&
objects.Any(x => x.Name.Equals("Topic", StringComparison.CurrentCultureIgnoreCase)) &&
objects.Any(x => x.Name.Equals("MessageId", StringComparison.CurrentCultureIgnoreCase)) &&
objects.Any(x => x.Name.Equals("JsonData", StringComparison.CurrentCultureIgnoreCase)) &&
objects.Any(x => x.Name.Equals("CorrelationId", StringComparison.CurrentCultureIgnoreCase));
}
}

28
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AbpCorrelationIdMiddleware.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
@ -20,16 +21,31 @@ public class AbpCorrelationIdMiddleware : IMiddleware, ITransientDependency
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var correlationId = _correlationIdProvider.Get();
var correlationId = GetCorrelationIdFromRequest(context);
try
using (_correlationIdProvider.Change(correlationId))
{
await next(context);
try
{
await next(context);
}
finally
{
CheckAndSetCorrelationIdOnResponse(context, _options, correlationId);
}
}
finally
}
protected virtual string GetCorrelationIdFromRequest(HttpContext context)
{
string correlationId = context.Request.Headers[_options.HttpHeaderName];
if (correlationId.IsNullOrEmpty())
{
CheckAndSetCorrelationIdOnResponse(context, _options, correlationId);
correlationId = Guid.NewGuid().ToString("N");
context.Request.Headers[_options.HttpHeaderName] = correlationId;
}
return correlationId;
}
protected virtual void CheckAndSetCorrelationIdOnResponse(

51
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Tracing/AspNetCoreCorrelationIdProvider.cs

@ -1,51 +0,0 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Tracing;
namespace Volo.Abp.AspNetCore.Tracing;
[Dependency(ReplaceServices = true)]
public class AspNetCoreCorrelationIdProvider : ICorrelationIdProvider, ITransientDependency
{
protected IHttpContextAccessor HttpContextAccessor { get; }
protected AbpCorrelationIdOptions Options { get; }
public AspNetCoreCorrelationIdProvider(
IHttpContextAccessor httpContextAccessor,
IOptions<AbpCorrelationIdOptions> options)
{
HttpContextAccessor = httpContextAccessor;
Options = options.Value;
}
public virtual string Get()
{
if (HttpContextAccessor.HttpContext?.Request?.Headers == null)
{
return CreateNewCorrelationId();
}
string correlationId = HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName];
if (correlationId.IsNullOrEmpty())
{
lock (HttpContextAccessor.HttpContext.Request.Headers)
{
if (correlationId.IsNullOrEmpty())
{
correlationId = CreateNewCorrelationId();
HttpContextAccessor.HttpContext.Request.Headers[Options.HttpHeaderName] = correlationId;
}
}
}
return correlationId;
}
protected virtual string CreateNewCorrelationId()
{
return Guid.NewGuid().ToString("N");
}
}

18
framework/src/Volo.Abp.Core/Volo/Abp/Tracing/DefaultCorrelationIdProvider.cs

@ -1,17 +1,27 @@
using System;
using System.Threading;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Tracing;
public class DefaultCorrelationIdProvider : ICorrelationIdProvider, ISingletonDependency
{
public string Get()
private readonly AsyncLocal<string?> _currentCorrelationId = new AsyncLocal<string?>();
private string? CorrelationId => _currentCorrelationId.Value;
public virtual string? Get()
{
return CreateNewCorrelationId();
return CorrelationId;
}
protected virtual string CreateNewCorrelationId()
public virtual IDisposable Change(string? correlationId)
{
return Guid.NewGuid().ToString("N");
var parent = CorrelationId;
_currentCorrelationId.Value = correlationId;
return new DisposeAction(() =>
{
_currentCorrelationId.Value = parent;
});
}
}

7
framework/src/Volo.Abp.Core/Volo/Abp/Tracing/ICorrelationIdProvider.cs

@ -1,9 +1,10 @@
using JetBrains.Annotations;
using System;
namespace Volo.Abp.Tracing;
public interface ICorrelationIdProvider
{
[NotNull]
string Get();
string? Get();
IDisposable Change(string? correlationId);
}

2
framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/IDaprSerializer.cs

@ -6,6 +6,8 @@ public interface IDaprSerializer
{
byte[] Serialize(object obj);
string SerializeToString(object obj);
object Deserialize(byte[] value, Type type);
object Deserialize(string value, Type type);

5
framework/src/Volo.Abp.Dapr/Volo/Abp/Dapr/Utf8JsonDaprSerializer.cs

@ -19,6 +19,11 @@ public class Utf8JsonDaprSerializer : IDaprSerializer, ITransientDependency
return Encoding.UTF8.GetBytes(_jsonSerializer.Serialize(obj));
}
public string SerializeToString(object obj)
{
return _jsonSerializer.Serialize(obj);
}
public object Deserialize(byte[] value, Type type)
{
return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value));

12
framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDatabaseInfo.cs

@ -24,4 +24,16 @@ public class AbpDatabaseInfo
DatabaseName = databaseName;
MappedConnections = new HashSet<string>();
}
/// <summary>
/// Shortcut method to add one or multiple connections to the <see cref="MappedConnections"/> collection.
/// </summary>
/// <param name="connectionNames"></param>
public void MapConnection(params string[] connectionNames)
{
foreach (var connectionName in connectionNames)
{
MappedConnections.AddIfNotContains(connectionName);
}
}
}

12
framework/src/Volo.Abp.Data/Volo/Abp/Data/AppliedDatabaseMigrationsEto.cs

@ -0,0 +1,12 @@
using System;
using Volo.Abp.EventBus;
namespace Volo.Abp.Data;
[Serializable]
[EventName("abp.data.applied_database_migrations")]
public class AppliedDatabaseMigrationsEto
{
public string DatabaseName { get; set; }
public Guid? TenantId { get; set; }
}

9
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs

@ -48,13 +48,20 @@ public class IncomingEventRecord :
public IncomingEventInfo ToIncomingEventInfo()
{
return new IncomingEventInfo(
var info = new IncomingEventInfo(
Id,
MessageId,
EventName,
EventData,
CreationTime
);
foreach (var property in ExtraProperties)
{
info.SetProperty(property.Key, property.Value);
}
return info;
}
public void MarkAsProcessed(DateTime processedTime)

9
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs

@ -41,11 +41,18 @@ public class OutgoingEventRecord :
public OutgoingEventInfo ToOutgoingEventInfo()
{
return new OutgoingEventInfo(
var info = new OutgoingEventInfo(
Id,
EventName,
EventData,
CreationTime
);
foreach (var property in ExtraProperties)
{
info.SetProperty(property.Key, property.Value);
}
return info;
}
}

318
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/DatabaseMigrationEventHandlerBase.cs

@ -0,0 +1,318 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore.Migrations;
public abstract class DatabaseMigrationEventHandlerBase<TDbContext> :
IDistributedEventHandler<TenantCreatedEto>,
IDistributedEventHandler<TenantConnectionStringUpdatedEto>,
IDistributedEventHandler<ApplyDatabaseMigrationsEto>,
ITransientDependency
where TDbContext : DbContext, IEfCoreDbContext
{
protected string DatabaseName { get; }
protected const string TryCountPropertyName = "__TryCount";
protected int MaxEventTryCount { get; set; } = 3;
/// <summary>
/// As milliseconds.
/// </summary>
protected int MinValueToWaitOnFailure { get; set; } = 5000;
/// <summary>
/// As milliseconds.
/// </summary>
protected int MaxValueToWaitOnFailure { get; set; } = 15000;
protected ICurrentTenant CurrentTenant { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected ITenantStore TenantStore { get; }
protected IDistributedEventBus DistributedEventBus { get; }
protected ILogger<DatabaseMigrationEventHandlerBase<TDbContext>> Logger { get; }
protected DatabaseMigrationEventHandlerBase(
string databaseName,
ICurrentTenant currentTenant,
IUnitOfWorkManager unitOfWorkManager,
ITenantStore tenantStore,
IDistributedEventBus distributedEventBus,
ILoggerFactory loggerFactory)
{
CurrentTenant = currentTenant;
UnitOfWorkManager = unitOfWorkManager;
TenantStore = tenantStore;
DatabaseName = databaseName;
DistributedEventBus = distributedEventBus;
Logger = loggerFactory.CreateLogger<DatabaseMigrationEventHandlerBase<TDbContext>>();
}
public virtual async Task HandleEventAsync(ApplyDatabaseMigrationsEto eventData)
{
if (eventData.DatabaseName != DatabaseName)
{
return;
}
var schemaMigrated = false;
try
{
schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.TenantId);
await SeedAsync(eventData.TenantId);
if (schemaMigrated)
{
await DistributedEventBus.PublishAsync(
new AppliedDatabaseMigrationsEto
{
DatabaseName = DatabaseName,
TenantId = eventData.TenantId
}
);
}
}
catch (Exception ex)
{
await HandleErrorOnApplyDatabaseMigrationAsync(eventData, ex);
}
await AfterApplyDatabaseMigrations(eventData, schemaMigrated);
}
protected virtual Task AfterApplyDatabaseMigrations(ApplyDatabaseMigrationsEto eventData, bool schemaMigrated)
{
return Task.CompletedTask;
}
public virtual async Task HandleEventAsync(TenantCreatedEto eventData)
{
var schemaMigrated = false;
try
{
schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.Id);
await SeedAsync(eventData.Id);
if (schemaMigrated)
{
await DistributedEventBus.PublishAsync(
new AppliedDatabaseMigrationsEto
{
DatabaseName = DatabaseName,
TenantId = eventData.Id
}
);
}
}
catch (Exception ex)
{
await HandleErrorTenantCreatedAsync(eventData, ex);
}
await AfterTenantCreated(eventData, schemaMigrated);
}
protected virtual Task AfterTenantCreated(TenantCreatedEto eventData, bool schemaMigrated)
{
return Task.CompletedTask;
}
public virtual async Task HandleEventAsync(TenantConnectionStringUpdatedEto eventData)
{
if (eventData.ConnectionStringName != DatabaseName &&
eventData.ConnectionStringName != Volo.Abp.Data.ConnectionStrings.DefaultConnectionStringName ||
eventData.NewValue.IsNullOrWhiteSpace())
{
return;
}
var schemaMigrated = false;
try
{
schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.Id);
await SeedAsync(eventData.Id);
if (schemaMigrated)
{
await DistributedEventBus.PublishAsync(
new AppliedDatabaseMigrationsEto
{
DatabaseName = DatabaseName,
TenantId = eventData.Id
}
);
}
}
catch (Exception ex)
{
await HandleErrorTenantConnectionStringUpdatedAsync(eventData, ex);
}
await AfterTenantConnectionStringUpdated(eventData, schemaMigrated);
}
protected virtual Task AfterTenantConnectionStringUpdated(TenantConnectionStringUpdatedEto eventData,
bool schemaMigrated)
{
return Task.CompletedTask;
}
protected virtual Task SeedAsync(Guid? tenantId)
{
return Task.CompletedTask;
}
/// <summary>
/// Apply pending EF Core schema migrations to the database.
/// Returns true if any migration has applied.
/// </summary>
protected virtual async Task<bool> MigrateDatabaseSchemaAsync(Guid? tenantId)
{
var result = false;
using (CurrentTenant.Change(tenantId))
{
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
{
async Task<bool> MigrateDatabaseSchemaWithDbContextAsync()
{
var dbContext = await uow.ServiceProvider
.GetRequiredService<IDbContextProvider<TDbContext>>()
.GetDbContextAsync();
if ((await dbContext.Database.GetPendingMigrationsAsync()).Any())
{
await dbContext.Database.MigrateAsync();
return true;
}
return false;
}
if (tenantId == null)
{
//Migrating the host database
Logger.LogInformation($"Migrating database of host. Database Name = {DatabaseName}");
result = await MigrateDatabaseSchemaWithDbContextAsync();
}
else
{
var tenantConfiguration = await TenantStore.FindAsync(tenantId.Value);
if (!tenantConfiguration.ConnectionStrings.Default.IsNullOrWhiteSpace() ||
!tenantConfiguration.ConnectionStrings.GetOrDefault(DatabaseName).IsNullOrWhiteSpace())
{
//Migrating the tenant database (only if tenant has a separate database)
Logger.LogInformation(
$"Migrating separate database of tenant. Database Name = {DatabaseName}, TenantId = {tenantId}");
result = await MigrateDatabaseSchemaWithDbContextAsync();
}
}
await uow.CompleteAsync();
}
}
return result;
}
protected virtual async Task HandleErrorOnApplyDatabaseMigrationAsync(
ApplyDatabaseMigrationsEto eventData,
Exception exception)
{
var tryCount = IncrementEventTryCount(eventData);
if (tryCount <= MaxEventTryCount)
{
Logger.LogWarning(
$"Could not apply database migrations. Re-queueing the operation. TenantId = {eventData.TenantId}, Database Name = {eventData.DatabaseName}.");
Logger.LogException(exception, LogLevel.Warning);
await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure));
await DistributedEventBus.PublishAsync(eventData);
}
else
{
Logger.LogError(
$"Could not apply database migrations. Canceling the operation. TenantId = {eventData.TenantId}, DatabaseName = {eventData.DatabaseName}.");
Logger.LogException(exception);
}
}
protected virtual async Task HandleErrorTenantCreatedAsync(
TenantCreatedEto eventData,
Exception exception)
{
var tryCount = IncrementEventTryCount(eventData);
if (tryCount <= MaxEventTryCount)
{
Logger.LogWarning(
$"Could not perform tenant created event. Re-queueing the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}.");
Logger.LogException(exception, LogLevel.Warning);
await Task.Delay(RandomHelper.GetRandom(5000, 15000));
await DistributedEventBus.PublishAsync(eventData);
}
else
{
Logger.LogError(
$"Could not perform tenant created event. Canceling the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}.");
Logger.LogException(exception);
}
}
protected virtual async Task HandleErrorTenantConnectionStringUpdatedAsync(
TenantConnectionStringUpdatedEto eventData,
Exception exception)
{
var tryCount = IncrementEventTryCount(eventData);
if (tryCount <= MaxEventTryCount)
{
Logger.LogWarning(
$"Could not perform tenant connection string updated event. Re-queueing the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}.");
Logger.LogException(exception, LogLevel.Warning);
await Task.Delay(RandomHelper.GetRandom(5000, 15000));
await DistributedEventBus.PublishAsync(eventData);
}
else
{
Logger.LogError(
$"Could not perform tenant connection string updated event. Canceling the operation. TenantId = {eventData.Id}, TenantName = {eventData.Name}.");
Logger.LogException(exception);
}
}
private static int GetEventTryCount(EtoBase eventData)
{
var tryCountAsString = eventData.Properties.GetOrDefault(TryCountPropertyName);
if (tryCountAsString.IsNullOrEmpty())
{
return 0;
}
return int.Parse(tryCountAsString);
}
private static void SetEventTryCount(EtoBase eventData, int count)
{
eventData.Properties[TryCountPropertyName] = count.ToString();
}
private static int IncrementEventTryCount(EtoBase eventData)
{
var count = GetEventTryCount(eventData) + 1;
SetEventTryCount(eventData, count);
return count;
}
}

145
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Migrations/EfCoreRuntimeDatabaseMigratorBase.cs

@ -0,0 +1,145 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore.Migrations;
public abstract class EfCoreRuntimeDatabaseMigratorBase<TDbContext> : ITransientDependency
where TDbContext : DbContext, IEfCoreDbContext
{
protected int MinValueToWaitOnFailure { get; set; } = 5000;
protected int MaxValueToWaitOnFailure { get; set; } = 15000;
protected string DatabaseName { get; }
/// <summary>
/// Enabling this might be inefficient if you have many tenants!
/// If disabled (default), tenant databases will be seeded only
/// if there is a schema migration applied to the host database.
/// If enabled, tenant databases will be seeded always on every service startup.
/// </summary>
protected bool AlwaysSeedTenantDatabases { get; set; } = false;
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected IServiceProvider ServiceProvider { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected IDistributedEventBus DistributedEventBus { get; }
protected ILogger<EfCoreRuntimeDatabaseMigratorBase<TDbContext>> Logger { get; }
protected EfCoreRuntimeDatabaseMigratorBase(
string databaseName,
IUnitOfWorkManager unitOfWorkManager,
IServiceProvider serviceProvider,
ICurrentTenant currentTenant,
IAbpDistributedLock abpDistributedLock,
IDistributedEventBus distributedEventBus,
ILoggerFactory loggerFactory)
{
DatabaseName = databaseName;
UnitOfWorkManager = unitOfWorkManager;
ServiceProvider = serviceProvider;
CurrentTenant = currentTenant;
DistributedLock = abpDistributedLock;
DistributedEventBus = distributedEventBus;
Logger = loggerFactory.CreateLogger<EfCoreRuntimeDatabaseMigratorBase<TDbContext>>();
}
public virtual async Task CheckAndApplyDatabaseMigrationsAsync()
{
await TryAsync(LockAndApplyDatabaseMigrationsAsync);
}
protected virtual async Task LockAndApplyDatabaseMigrationsAsync()
{
Logger.LogInformation($"Trying to acquire the distributed lock for database migration: {DatabaseName}.");
var schemaMigrated = false;
await using (var handle = await DistributedLock.TryAcquireAsync("DatabaseMigration_" + DatabaseName))
{
if (handle is null)
{
Logger.LogInformation($"Distributed lock could not be acquired for database migration: {DatabaseName}. Operation cancelled.");
return;
}
Logger.LogInformation($"Distributed lock is acquired for database migration: {DatabaseName}...");
using (CurrentTenant.Change(null))
{
// Create database tables if needed
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
{
var dbContext = await ServiceProvider
.GetRequiredService<IDbContextProvider<TDbContext>>()
.GetDbContextAsync();
var pendingMigrations = await dbContext
.Database
.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
await dbContext.Database.MigrateAsync();
schemaMigrated = true;
}
await uow.CompleteAsync();
}
}
await SeedAsync();
if (schemaMigrated || AlwaysSeedTenantDatabases)
{
await DistributedEventBus.PublishAsync(
new AppliedDatabaseMigrationsEto
{
DatabaseName = DatabaseName,
TenantId = null
}
);
}
}
Logger.LogInformation($"Distributed lock has been released for database migration: {DatabaseName}...");
}
protected virtual Task SeedAsync()
{
return Task.CompletedTask;
}
protected virtual async Task TryAsync(Func<Task> task, int maxTryCount = 3)
{
try
{
await task();
}
catch (Exception ex)
{
maxTryCount--;
if (maxTryCount <= 0)
{
throw;
}
Logger.LogWarning($"{ex.GetType().Name} has been thrown. The operation will be tried {maxTryCount} times more. Exception:\n{ex.Message}. Stack Trace:\n{ex.StackTrace}");
await Task.Delay(RandomHelper.GetRandom(MinValueToWaitOnFailure, MaxValueToWaitOnFailure));
await TryAsync(task, maxTryCount);
}
}
}

11
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
namespace Volo.Abp.EventBus.Distributed;
@ -40,4 +41,14 @@ public class IncomingEventInfo : IHasExtraProperties
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
public void SetCorrelationId(string correlationId)
{
ExtraProperties[EventBusConsts.CorrelationIdHeaderName] = correlationId;
}
public string GetCorrelationId()
{
return ExtraProperties.GetOrDefault(EventBusConsts.CorrelationIdHeaderName)?.ToString();
}
}

13
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
namespace Volo.Abp.EventBus.Distributed;
@ -36,4 +37,14 @@ public class OutgoingEventInfo : IHasExtraProperties
ExtraProperties = new ExtraPropertyDictionary();
this.SetDefaultsForExtraProperties();
}
}
public void SetCorrelationId(string correlationId)
{
ExtraProperties[EventBusConsts.CorrelationIdHeaderName] = correlationId;
}
public string GetCorrelationId()
{
return ExtraProperties.GetOrDefault(EventBusConsts.CorrelationIdHeaderName)?.ToString();
}
}

6
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/EventBusConsts.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.EventBus;
public static class EventBusConsts
{
public const string CorrelationIdHeaderName = "X-Correlation-Id";
}

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

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Messaging.ServiceBus;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@ -14,6 +15,7 @@ using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Azure;
@ -42,7 +44,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
IAzureServiceBusMessageConsumerFactory messageConsumerFactory,
IPublisherPool publisherPool,
IEventHandlerInvoker eventHandlerInvoker,
ILocalEventBus localEventBus)
ILocalEventBus localEventBus,
ICorrelationIdProvider correlationIdProvider)
: base(serviceScopeFactory,
currentTenant,
unitOfWorkManager,
@ -50,7 +53,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
guidGenerator,
clock,
eventHandlerInvoker,
localEventBus)
localEventBus,
correlationIdProvider)
{
Options = abpAzureEventBusOptions.Value;
Serializer = serializer;
@ -86,24 +90,30 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventData = Serializer.Deserialize(message.Body.ToArray(), eventType);
if (await AddToInboxAsync(message.MessageId, eventName, eventType, eventData))
if (await AddToInboxAsync(message.MessageId, eventName, eventType, eventData, message.CorrelationId))
{
return;
}
await TriggerHandlersDirectAsync(eventType, eventData);
using (CorrelationIdProvider.Change(message.CorrelationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
}
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.Id);
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.GetCorrelationId(), outgoingEvent.Id);
}
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
@ -125,18 +135,23 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
message.MessageId = outgoingEvent.Id.ToString();
}
message.CorrelationId = outgoingEvent.GetCorrelationId();
if (!messageBatch.TryAddMessage(message))
{
throw new AbpException(
"The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number");
}
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
}
await publisher.SendMessagesAsync(messageBatch);
@ -152,7 +167,10 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
}
if (exceptions.Any())
{
ThrowOriginalExceptions(eventType, exceptions);
@ -244,12 +262,13 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
var body = Serializer.Serialize(eventData);
return PublishAsync(eventName, body, null);
return PublishAsync(eventName, body, CorrelationIdProvider.Get(), null);
}
protected virtual async Task PublishAsync(
string eventName,
byte[] body,
[CanBeNull] string correlationId,
Guid? eventId)
{
var message = new ServiceBusMessage(body)
@ -262,6 +281,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
message.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N");
}
message.CorrelationId = correlationId;
var publisher = await PublisherPool.GetAsync(
Options.TopicName,
Options.ConnectionName);

23
framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/AbpDaprEventData.cs

@ -0,0 +1,23 @@
namespace Volo.Abp.EventBus.Dapr;
public class AbpDaprEventData
{
public string PubSubName { get; set; }
public string Topic { get; set; }
public string MessageId { get; set; }
public string JsonData { get; set; }
public string CorrelationId { get; set; }
public AbpDaprEventData(string pubSubName, string topic, string messageId, string jsonData, string correlationId)
{
PubSubName = pubSubName;
Topic = topic;
MessageId = messageId;
JsonData = jsonData;
CorrelationId = correlationId;
}
}

75
framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs

@ -13,6 +13,7 @@ using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Dapr;
@ -39,8 +40,17 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
IDaprSerializer serializer,
IOptions<AbpDaprEventBusOptions> daprEventBusOptions,
IAbpDaprClientFactory daprClientFactory,
ILocalEventBus localEventBus)
: base(serviceScopeFactory, currentTenant, unitOfWorkManager, abpDistributedEventBusOptions, guidGenerator, clock, eventHandlerInvoker, localEventBus)
ILocalEventBus localEventBus,
ICorrelationIdProvider correlationIdProvider)
: base(serviceScopeFactory,
currentTenant,
unitOfWorkManager,
abpDistributedEventBusOptions,
guidGenerator,
clock,
eventHandlerInvoker,
localEventBus,
correlationIdProvider)
{
Serializer = serializer;
DaprEventBusOptions = daprEventBusOptions.Value;
@ -119,9 +129,9 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
}
protected override async Task PublishToEventBusAsync(Type eventType, object eventData)
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishToDaprAsync(eventType, eventData);
await PublishToDaprAsync(eventType, eventData, null, CorrelationIdProvider.Get());
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
@ -141,43 +151,52 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
return handlerFactoryList.ToArray();
}
public override async Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)));
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
}
public override async Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
{
var outgoingEventArray = outgoingEvents.ToArray();
foreach (var outgoingEvent in outgoingEventArray)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)));
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, GetEventType(outgoingEvent.EventName)), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
}
}
public virtual async Task TriggerHandlersAsync(string messageId, Type eventType, object eventData)
public virtual async Task TriggerHandlersAsync(Type eventType, object eventData, string messageId = null, string correlationId = null)
{
if (await AddToInboxAsync(messageId, EventNameAttribute.GetNameOrDefault(eventType), eventType, eventData))
if (await AddToInboxAsync(messageId, EventNameAttribute.GetNameOrDefault(eventType), eventType, eventData, correlationId))
{
return;
}
await TriggerHandlersDirectAsync(eventType, eventData);
using (CorrelationIdProvider.Change(correlationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
}
public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig)
@ -190,7 +209,10 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
}
if (exceptions.Any())
{
ThrowOriginalExceptions(eventType, exceptions);
@ -226,15 +248,16 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
return EventTypes.GetOrDefault(eventName);
}
protected virtual async Task PublishToDaprAsync(Type eventType, object eventData)
protected virtual async Task PublishToDaprAsync(Type eventType, object eventData, Guid? messageId = null, string correlationId = null)
{
await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData);
await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData, messageId, correlationId);
}
protected virtual async Task PublishToDaprAsync(string eventName, object eventData)
protected virtual async Task PublishToDaprAsync(string eventName, object eventData, Guid? messageId = null, string correlationId = null)
{
var client = DaprClientFactory.Create();
await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: eventData);
var data = new AbpDaprEventData(DaprEventBusOptions.PubSubName, eventName, (messageId ?? GuidGenerator.Create()).ToString("N"), Serializer.SerializeToString(eventData), correlationId);
await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: data);
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)

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

@ -14,6 +14,7 @@ using Volo.Abp.Kafka;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Kafka;
@ -42,7 +43,8 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
IGuidGenerator guidGenerator,
IClock clock,
IEventHandlerInvoker eventHandlerInvoker,
ILocalEventBus localEventBus)
ILocalEventBus localEventBus,
ICorrelationIdProvider correlationIdProvider)
: base(
serviceScopeFactory,
currentTenant,
@ -51,7 +53,8 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
guidGenerator,
clock,
eventHandlerInvoker,
localEventBus)
localEventBus,
correlationIdProvider)
{
AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value;
MessageConsumerFactory = messageConsumerFactory;
@ -84,13 +87,17 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var messageId = message.GetMessageId();
var eventData = Serializer.Deserialize(message.Value, eventType);
var correlationId = message.GetCorrelationId();
if (await AddToInboxAsync(messageId, eventName, eventType, eventData))
if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId))
{
return;
}
await TriggerHandlersDirectAsync(eventType, eventData);
using (CorrelationIdProvider.Change(correlationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
}
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
@ -163,14 +170,21 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
var headers = new Headers
{
{ "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) }
};
if (CorrelationIdProvider.Get() != null)
{
headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(CorrelationIdProvider.Get()!));
}
await PublishAsync(
AbpKafkaEventBusOptions.TopicName,
eventType,
eventData,
new Headers
{
{ "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) }
}
headers
);
}
@ -179,25 +193,34 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
public override async Task PublishFromOutboxAsync(
public async override Task PublishFromOutboxAsync(
OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
var headers = new Headers
{
{ "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) }
};
if (outgoingEvent.GetCorrelationId() != null)
{
headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!));
}
await PublishAsync(
AbpKafkaEventBusOptions.TopicName,
outgoingEvent.EventName,
outgoingEvent.EventData,
new Headers
{
{ "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) }
}
headers
);
}
@ -214,12 +237,20 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{ "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)}
};
await TriggerDistributedEventSentAsync(new DistributedEventSent()
if (outgoingEvent.GetCorrelationId() != null)
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!));
}
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
producer.Produce(
AbpKafkaEventBusOptions.TopicName,
@ -244,7 +275,10 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
}
if (exceptions.Any())
{
ThrowOriginalExceptions(eventType, exceptions);

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

@ -15,4 +15,16 @@ public static class MessageExtensions
return messageId;
}
public static string GetCorrelationId<TKey, TValue>(this Message<TKey, TValue> message)
{
string correlationId = null;
if (message.Headers.TryGetLastBytes(EventBusConsts.CorrelationIdHeaderName, out var correlationIdBytes))
{
correlationId = System.Text.Encoding.UTF8.GetString(correlationIdBytes);
}
return correlationId;
}
}

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

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
@ -15,6 +16,7 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.RabbitMQ;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.RabbitMq;
@ -49,7 +51,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
IGuidGenerator guidGenerator,
IClock clock,
IEventHandlerInvoker eventHandlerInvoker,
ILocalEventBus localEventBus)
ILocalEventBus localEventBus,
ICorrelationIdProvider correlationIdProvider)
: base(
serviceScopeFactory,
currentTenant,
@ -58,7 +61,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
guidGenerator,
clock,
eventHandlerInvoker,
localEventBus)
localEventBus,
correlationIdProvider)
{
ConnectionPool = connectionPool;
Serializer = serializer;
@ -103,12 +107,16 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType);
if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventData))
var correlationId = ea.BasicProperties.CorrelationId;
if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventData, correlationId))
{
return;
}
await TriggerHandlersDirectAsync(eventType, eventData);
using (CorrelationIdProvider.Change(correlationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
}
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
@ -186,7 +194,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishAsync(eventType, eventData, null);
await PublishAsync(eventType, eventData, correlationId: CorrelationIdProvider.Get());
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
@ -194,18 +202,21 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
public override async Task PublishFromOutboxAsync(
public async override Task PublishFromOutboxAsync(
OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id);
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, eventId: outgoingEvent.Id, correlationId: outgoingEvent.GetCorrelationId());
}
public async override Task PublishManyFromOutboxAsync(
@ -219,19 +230,22 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
foreach (var outgoingEvent in outgoingEventArray)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishAsync(
channel,
outgoingEvent.EventName,
outgoingEvent.EventData,
properties: null,
eventId: outgoingEvent.Id);
eventId: outgoingEvent.Id,
correlationId: outgoingEvent.GetCorrelationId());
}
channel.WaitForConfirmsOrDie();
@ -250,7 +264,10 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
}
if (exceptions.Any())
{
ThrowOriginalExceptions(eventType, exceptions);
@ -262,28 +279,29 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
return Serializer.Serialize(eventData);
}
public Task PublishAsync(
public virtual Task PublishAsync(
Type eventType,
object eventData,
IBasicProperties properties,
Dictionary<string, object> headersArguments = null)
Dictionary<string, object> headersArguments = null,
Guid? eventId = null,
[CanBeNull] string correlationId = null)
{
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var body = Serializer.Serialize(eventData);
return PublishAsync(eventName, body, properties, headersArguments);
return PublishAsync( eventName, body, headersArguments, eventId, correlationId);
}
protected virtual Task PublishAsync(
string eventName,
byte[] body,
IBasicProperties properties,
Dictionary<string, object> headersArguments = null,
Guid? eventId = null)
Guid? eventId = null,
[CanBeNull] string correlationId = null)
{
using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
{
return PublishAsync(channel, eventName, body, properties, headersArguments, eventId);
return PublishAsync(channel, eventName, body, headersArguments, eventId, correlationId);
}
}
@ -291,23 +309,25 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe
IModel channel,
string eventName,
byte[] body,
IBasicProperties properties,
Dictionary<string, object> headersArguments = null,
Guid? eventId = null)
Guid? eventId = null,
[CanBeNull] string correlationId = null)
{
EnsureExchangeExists(channel);
if (properties == null)
{
properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
}
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;
if (properties.MessageId.IsNullOrEmpty())
{
properties.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N");
}
if (correlationId != null)
{
properties.CorrelationId = correlationId;
}
SetEventMessageHeaders(properties, headersArguments);
channel.BasicPublish(

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

@ -16,6 +16,7 @@ using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Rebus;
@ -43,7 +44,8 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
IGuidGenerator guidGenerator,
IClock clock,
IEventHandlerInvoker eventHandlerInvoker,
ILocalEventBus localEventBus) :
ILocalEventBus localEventBus,
ICorrelationIdProvider correlationIdProvider) :
base(
serviceScopeFactory,
currentTenant,
@ -52,7 +54,8 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
guidGenerator,
clock,
eventHandlerInvoker,
localEventBus)
localEventBus,
correlationIdProvider)
{
Rebus = rebus;
Serializer = serializer;
@ -144,18 +147,27 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
var messageId = MessageContext.Current.TransportMessage.GetMessageId();
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var correlationId = MessageContext.Current.Headers.GetOrDefault(EventBusConsts.CorrelationIdHeaderName);
if (await AddToInboxAsync(messageId, eventName, eventType, eventData))
if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId))
{
return;
}
await TriggerHandlersDirectAsync(eventType, eventData);
using (CorrelationIdProvider.Change(correlationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishAsync(eventType, eventData);
var headers = new Dictionary<string, string>();
if (CorrelationIdProvider.Get() != null)
{
headers.Add(EventBusConsts.CorrelationIdHeaderName, CorrelationIdProvider.Get());
}
await PublishAsync(eventType, eventData, headersArguments: headers);
}
protected virtual async Task PublishAsync(
@ -234,21 +246,29 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return false;
}
public override async Task PublishFromOutboxAsync(
public async override Task PublishFromOutboxAsync(
OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig)
{
var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName);
var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType);
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent() {
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id);
var headers = new Dictionary<string, string>();
if (outgoingEvent.GetCorrelationId() != null)
{
headers.Add(EventBusConsts.CorrelationIdHeaderName, outgoingEvent.GetCorrelationId());
}
await PublishAsync(eventType, eventData, eventId: outgoingEvent.Id, headersArguments: headers);
}
public async override Task PublishManyFromOutboxAsync(IEnumerable<OutgoingEventInfo> outgoingEvents, OutboxConfig outboxConfig)
@ -259,12 +279,15 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
foreach (var outgoingEvent in outgoingEventArray)
{
await TriggerDistributedEventSentAsync(new DistributedEventSent()
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Outbox,
EventName = outgoingEvent.EventName,
EventData = outgoingEvent.EventData
});
}
await PublishFromOutboxAsync(outgoingEvent, outboxConfig);
}
@ -285,7 +308,10 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
await TriggerHandlersFromInboxAsync(eventType, eventData, exceptions, inboxConfig);
}
if (exceptions.Any())
{
ThrowOriginalExceptions(eventType, exceptions);

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

@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Uow;
namespace Volo.Abp.EventBus.Distributed;
@ -18,6 +20,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
protected IClock Clock { get; }
protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; }
protected ILocalEventBus LocalEventBus { get; }
protected ICorrelationIdProvider CorrelationIdProvider { get; }
protected DistributedEventBusBase(
IServiceScopeFactory serviceScopeFactory,
@ -27,7 +30,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
IGuidGenerator guidGenerator,
IClock clock,
IEventHandlerInvoker eventHandlerInvoker,
ILocalEventBus localEventBus) : base(
ILocalEventBus localEventBus,
ICorrelationIdProvider correlationIdProvider) : base(
serviceScopeFactory,
currentTenant,
unitOfWorkManager,
@ -37,6 +41,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
Clock = clock;
AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value;
LocalEventBus = localEventBus;
CorrelationIdProvider = correlationIdProvider;
}
public IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler) where TEvent : class
@ -129,14 +134,14 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
EventData = eventData
});
await eventOutbox.EnqueueAsync(
new OutgoingEventInfo(
GuidGenerator.Create(),
eventName,
Serialize(eventData),
Clock.Now
)
var outgoingEventInfo = new OutgoingEventInfo(
GuidGenerator.Create(),
eventName,
Serialize(eventData),
Clock.Now
);
outgoingEventInfo.SetCorrelationId(CorrelationIdProvider.Get());
await eventOutbox.EnqueueAsync(outgoingEventInfo);
return true;
}
}
@ -153,7 +158,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
string messageId,
string eventName,
Type eventType,
object eventData)
object eventData,
[CanBeNull] string correlationId)
{
if (AbpDistributedEventBusOptions.Inboxes.Count <= 0)
{
@ -177,22 +183,25 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
}
}
await TriggerDistributedEventReceivedAsync(new DistributedEventReceived
using (CorrelationIdProvider.Change(correlationId))
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
});
await eventInbox.EnqueueAsync(
new IncomingEventInfo(
GuidGenerator.Create(),
messageId,
eventName,
Serialize(eventData),
Clock.Now
)
await TriggerDistributedEventReceivedAsync(new DistributedEventReceived
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
});
}
var incomingEventInfo = new IncomingEventInfo(
GuidGenerator.Create(),
messageId,
eventName,
Serialize(eventData),
Clock.Now
);
incomingEventInfo.SetCorrelationId(correlationId);
await eventInbox.EnqueueAsync(incomingEventInfo);
}
}
}

9
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs

@ -48,13 +48,20 @@ public class IncomingEventRecord :
public IncomingEventInfo ToIncomingEventInfo()
{
return new IncomingEventInfo(
var info = new IncomingEventInfo(
Id,
MessageId,
EventName,
EventData,
CreationTime
);
foreach (var property in ExtraProperties)
{
info.SetProperty(property.Key, property.Value);
}
return info;
}
public void MarkAsProcessed(DateTime processedTime)

9
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs

@ -41,11 +41,18 @@ public class OutgoingEventRecord :
public OutgoingEventInfo ToOutgoingEventInfo()
{
return new OutgoingEventInfo(
var info = new OutgoingEventInfo(
Id,
EventName,
EventData,
CreationTime
);
foreach (var property in ExtraProperties)
{
info.SetProperty(property.Key, property.Value);
}
return info;
}
}

2
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTemplateRenderer.cs

@ -29,7 +29,7 @@ public class AbpTemplateRenderer : ITemplateRenderer, ITransientDependency
string cultureName = null,
Dictionary<string, object> globalContext = null)
{
var templateDefinition = TemplateDefinitionManager.Get(templateName);
var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName);
var renderEngine = templateDefinition.RenderEngine;

3
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs

@ -12,10 +12,13 @@ public class AbpTextTemplatingOptions
public string DefaultRenderingEngine { get; set; }
public HashSet<string> DeletedTemplates { get; }
public AbpTextTemplatingOptions()
{
DefinitionProviders = new TypeList<ITemplateDefinitionProvider>();
ContentContributors = new TypeList<ITemplateContentContributor>();
RenderingEngines = new Dictionary<string, Type>();
DeletedTemplates = new HashSet<string>();
}
}

14
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IDynamicTemplateDefinitionStore.cs

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.TextTemplating;
public interface IDynamicTemplateDefinitionStore
{
Task<TemplateDefinition> GetAsync([NotNull] string name);
Task<IReadOnlyList<TemplateDefinition>> GetAllAsync();
Task<TemplateDefinition> GetOrNullAsync(string name);
}

14
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/IStaticTemplateDefinitionStore.cs

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.TextTemplating;
public interface IStaticTemplateDefinitionStore
{
Task<TemplateDefinition> GetAsync([NotNull] string name);
Task<IReadOnlyList<TemplateDefinition>> GetAllAsync();
Task<TemplateDefinition> GetOrNullAsync(string name);
}

13
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs

@ -1,16 +1,17 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.TextTemplating;
public interface ITemplateDefinitionManager
{
[NotNull]
TemplateDefinition Get([NotNull] string name);
[ItemNotNull]
Task<TemplateDefinition> GetAsync([NotNull] string name);
[NotNull]
IReadOnlyList<TemplateDefinition> GetAll();
[ItemNotNull]
Task<IReadOnlyList<TemplateDefinition>> GetAllAsync();
[CanBeNull]
TemplateDefinition GetOrNull(string name);
[ItemCanBeNull]
Task<TemplateDefinition> GetOrNullAsync(string name);
}

29
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/NullIDynamicTemplateDefinitionStore.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TextTemplating;
public class NullIDynamicTemplateDefinitionStore : IDynamicTemplateDefinitionStore, ISingletonDependency
{
private readonly static Task<TemplateDefinition> CachedTemplateResult = Task.FromResult((TemplateDefinition)null);
private readonly static Task<IReadOnlyList<TemplateDefinition>> CachedFeaturesResult = Task.FromResult((IReadOnlyList<TemplateDefinition>)Array.Empty<TemplateDefinition>().ToImmutableList());
public Task<TemplateDefinition> GetAsync(string name)
{
return CachedTemplateResult;
}
public Task<IReadOnlyList<TemplateDefinition>> GetAllAsync()
{
return CachedFeaturesResult;
}
public Task<TemplateDefinition> GetOrNullAsync(string name)
{
return CachedTemplateResult;
}
}

83
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TextTemplating;
public class StaticTemplateDefinitionStore : IStaticTemplateDefinitionStore, ISingletonDependency
{
protected Lazy<IDictionary<string, TemplateDefinition>> TemplateDefinitions { get; }
protected AbpTextTemplatingOptions Options { get; }
protected IServiceProvider ServiceProvider { get; }
public StaticTemplateDefinitionStore(IOptions<AbpTextTemplatingOptions> options, IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Options = options.Value;
TemplateDefinitions = new Lazy<IDictionary<string, TemplateDefinition>>(CreateTextTemplateDefinitions, true);
}
public virtual Task<TemplateDefinition> GetAsync(string name)
{
Check.NotNull(name, nameof(name));
var template = GetOrNullAsync(name);
if (template == null)
{
throw new AbpException("Undefined template: " + name);
}
return template;
}
public virtual Task<IReadOnlyList<TemplateDefinition>> GetAllAsync()
{
return Task.FromResult<IReadOnlyList<TemplateDefinition>>(TemplateDefinitions.Value.Values.ToImmutableList());
}
public virtual Task<TemplateDefinition> GetOrNullAsync(string name)
{
return Task.FromResult(TemplateDefinitions.Value.GetOrDefault(name));
}
protected virtual IDictionary<string, TemplateDefinition> CreateTextTemplateDefinitions()
{
var templates = new Dictionary<string, TemplateDefinition>();
using (var scope = ServiceProvider.CreateScope())
{
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider)
.ToList();
var context = new TemplateDefinitionContext(templates);
foreach (var provider in providers)
{
provider.PreDefine(context);
}
foreach (var provider in providers)
{
provider.Define(context);
}
foreach (var provider in providers)
{
provider.PostDefine(context);
}
}
return templates;
}
}

6
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateContentProvider.cs

@ -26,14 +26,14 @@ public class TemplateContentProvider : ITemplateContentProvider, ITransientDepen
_templateDefinitionManager = templateDefinitionManager;
}
public virtual Task<string> GetContentOrNullAsync(
public virtual async Task<string> GetContentOrNullAsync(
[NotNull] string templateName,
[CanBeNull] string cultureName = null,
bool tryDefaults = true,
bool useCurrentCultureIfCultureNameIsNull = true)
{
var template = _templateDefinitionManager.Get(templateName);
return GetContentOrNullAsync(template, cultureName);
var template = await _templateDefinitionManager.GetAsync(templateName);
return await GetContentOrNullAsync(template, cultureName);
}
public virtual async Task<string> GetContentOrNullAsync(

18
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinition.cs

@ -28,7 +28,7 @@ public class TemplateDefinition : IHasNameWithLocalizableDisplayName
public string Layout { get; set; }
[CanBeNull]
public Type LocalizationResource { get; set; }
public string LocalizationResourceName { get; set; }
public bool IsInlineLocalized { get; set; }
@ -60,14 +60,26 @@ public class TemplateDefinition : IHasNameWithLocalizableDisplayName
public TemplateDefinition(
[NotNull] string name,
[CanBeNull] Type localizationResource = null,
[NotNull] Type localizationResource,
[CanBeNull] ILocalizableString displayName = null,
bool isLayout = false,
string layout = null,
string defaultCultureName = null)
: this(name, LocalizationResourceNameAttribute.GetName(localizationResource), displayName, isLayout, layout, defaultCultureName)
{
}
public TemplateDefinition(
[NotNull] string name,
[CanBeNull] string localizationResourceName = null,
[CanBeNull] ILocalizableString displayName = null,
bool isLayout = false,
string layout = null,
string defaultCultureName = null)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength);
LocalizationResource = localizationResource;
LocalizationResourceName = localizationResourceName;
DisplayName = displayName ?? new FixedLocalizableString(Name);
IsLayout = isLayout;
Layout = layout;

81
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs

@ -1,85 +1,50 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.TextTemplating;
public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency
{
protected Lazy<IDictionary<string, TemplateDefinition>> TemplateDefinitions { get; }
protected readonly IStaticTemplateDefinitionStore StaticStore;
protected readonly IDynamicTemplateDefinitionStore DynamicStore;
protected AbpTextTemplatingOptions Options { get; }
protected IServiceProvider ServiceProvider { get; }
public TemplateDefinitionManager(
IOptions<AbpTextTemplatingOptions> options,
IServiceProvider serviceProvider)
public TemplateDefinitionManager(IStaticTemplateDefinitionStore staticStore, IDynamicTemplateDefinitionStore dynamicStore)
{
ServiceProvider = serviceProvider;
Options = options.Value;
TemplateDefinitions =
new Lazy<IDictionary<string, TemplateDefinition>>(CreateTextTemplateDefinitions, true);
StaticStore = staticStore;
DynamicStore = dynamicStore;
}
public virtual TemplateDefinition Get(string name)
public virtual async Task<TemplateDefinition> GetAsync(string name)
{
Check.NotNull(name, nameof(name));
var template = GetOrNull(name);
if (template == null)
var permission = await GetOrNullAsync(name);
if (permission == null)
{
throw new AbpException("Undefined template: " + name);
throw new AbpException("Undefined Template: " + name);
}
return template;
return permission;
}
public virtual IReadOnlyList<TemplateDefinition> GetAll()
public virtual async Task<TemplateDefinition> GetOrNullAsync(string name)
{
return TemplateDefinitions.Value.Values.ToImmutableList();
}
Check.NotNull(name, nameof(name));
public virtual TemplateDefinition GetOrNull(string name)
{
return TemplateDefinitions.Value.GetOrDefault(name);
return await StaticStore.GetOrNullAsync(name) ?? await DynamicStore.GetOrNullAsync(name);
}
protected virtual IDictionary<string, TemplateDefinition> CreateTextTemplateDefinitions()
public virtual async Task<IReadOnlyList<TemplateDefinition>> GetAllAsync()
{
var templates = new Dictionary<string, TemplateDefinition>();
using (var scope = ServiceProvider.CreateScope())
{
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider)
.ToList();
var context = new TemplateDefinitionContext(templates);
var staticTemplates = await StaticStore.GetAllAsync();
var staticTemplateNames = staticTemplates
.Select(p => p.Name)
.ToImmutableHashSet();
foreach (var provider in providers)
{
provider.PreDefine(context);
}
foreach (var provider in providers)
{
provider.Define(context);
}
foreach (var provider in providers)
{
provider.PostDefine(context);
}
}
var dynamicTemplates = await DynamicStore.GetAllAsync();
return templates;
/* We prefer static Templates over dynamics */
return staticTemplates.Concat(dynamicTemplates.Where(d => !staticTemplateNames.Contains(d.Name))).ToImmutableList();
}
}

4
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs

@ -31,9 +31,9 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine
protected virtual IStringLocalizer GetLocalizerOrNull(TemplateDefinition templateDefinition)
{
if (templateDefinition.LocalizationResource != null)
if (templateDefinition.LocalizationResourceName != null)
{
return StringLocalizerFactory.Create(templateDefinition.LocalizationResource);
return StringLocalizerFactory.CreateByResourceName(templateDefinition.LocalizationResourceName);
}
return StringLocalizerFactory.CreateDefaultOrNull();

11
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs

@ -5,10 +5,12 @@ namespace Volo.Abp.TextTemplating.VirtualFiles;
public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader
{
private IFileInfo _fileInfo;
private string _content;
public async Task ReadContentsAsync(IFileInfo fileInfo)
{
_fileInfo = fileInfo;
_content = await fileInfo.ReadAsStringAsync();
}
@ -21,4 +23,13 @@ public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentR
return null;
}
public TemplateContentFileInfo GetFile()
{
return new TemplateContentFileInfo()
{
FileName = _fileInfo.Name,
FileContent = _content
};
}
}

8
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileInfo.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.TextTemplating.VirtualFiles;
public class TemplateContentFileInfo
{
public string FileName { get; set; }
public string FileContent { get; set; }
}

56
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider.cs

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.TextTemplating.VirtualFiles;
public class TemplateContentFileProvider : ITransientDependency
{
protected IVirtualFileProvider VirtualFileProvider { get; }
public TemplateContentFileProvider(IVirtualFileProvider virtualFileProvider)
{
VirtualFileProvider = virtualFileProvider;
}
public async Task<List<TemplateContentFileInfo>> GetFilesAsync(TemplateDefinition templateDefinition)
{
var files = new List<TemplateContentFileInfo>();
var virtualPath = templateDefinition.GetVirtualFilePathOrNull();
if (virtualPath == null)
{
return files;
}
var fileInfo = VirtualFileProvider.GetFileInfo(virtualPath);
if (!fileInfo.Exists)
{
var directoryContents = VirtualFileProvider.GetDirectoryContents(virtualPath);
if (!directoryContents.Exists)
{
throw new AbpException("Could not find a file/folder at the location: " + virtualPath);
}
fileInfo = new VirtualDirectoryFileInfo(virtualPath, virtualPath, DateTimeOffset.UtcNow);
}
if (fileInfo.IsDirectory)
{
//TODO: Configure file extensions.
var folderReader = new VirtualFolderLocalizedTemplateContentReader(new[] { ".tpl", ".cshtml" });
await folderReader.ReadContentsAsync(VirtualFileProvider, virtualPath);
files.AddRange(folderReader.GetFiles());
}
else
{
var singleFileReader = new FileInfoLocalizedTemplateContentReader();
await singleFileReader.ReadContentsAsync(fileInfo);
files.Add(singleFileReader.GetFile());
}
return files;
}
}

18
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.VirtualFileSystem;
@ -8,7 +9,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles;
public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader
{
private Dictionary<string, string> _dictionary;
private Dictionary<string, TemplateContentFileInfo> _dictionary;
private readonly string[] _fileExtension;
public VirtualFolderLocalizedTemplateContentReader(string[] fileExtension)
@ -20,7 +21,7 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon
IVirtualFileProvider virtualFileProvider,
string virtualPath)
{
_dictionary = new Dictionary<string, string>();
_dictionary = new Dictionary<string, TemplateContentFileInfo>();
var directoryContents = virtualFileProvider.GetDirectoryContents(virtualPath);
if (!directoryContents.Exists)
@ -35,7 +36,11 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon
continue;
}
_dictionary.Add(file.Name.RemovePostFix(_fileExtension), await file.ReadAsStringAsync());
_dictionary.Add(file.Name.RemovePostFix(_fileExtension), new TemplateContentFileInfo()
{
FileName = file.Name,
FileContent = await file.ReadAsStringAsync()
});
}
}
@ -46,6 +51,11 @@ public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateCon
return null;
}
return _dictionary.GetOrDefault(cultureName);
return _dictionary.GetOrDefault(cultureName)?.FileContent;
}
public List<TemplateContentFileInfo> GetFiles()
{
return _dictionary.Values.ToList();
}
}

5
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/DefaultAbpCompiledViewProvider.cs

@ -45,6 +45,11 @@ public class DefaultAbpCompiledViewProvider : IAbpCompiledViewProvider, ITransie
}
var templateContent = await _templateContentProvider.GetContentOrNullAsync(templateDefinition);
if (templateContent == null)
{
throw new AbpException($"Razor template content of {templateDefinition.Name} is null!");
}
return CachedAssembles.GetOrAdd((templateDefinition.Name + templateContent).ToMd5(), await CreateAssembly(templateContent));
}

2
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs

@ -74,7 +74,7 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi
Dictionary<string, object> globalContext,
object model = null)
{
var templateDefinition = TemplateDefinitionManager.Get(templateName);
var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName);
var renderedContent = await RenderSingleTemplateAsync(
templateDefinition,

2
framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs

@ -61,7 +61,7 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran
Dictionary<string, object> globalContext,
object model = null)
{
var templateDefinition = TemplateDefinitionManager.Get(templateName);
var templateDefinition = await TemplateDefinitionManager.GetAsync(templateName);
var renderedContent = await RenderSingleTemplateAsync(
templateDefinition,

20
framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs

@ -75,7 +75,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
if (Options != null)
{
throw new AbpException("This unit of work is already initialized before!");
throw new AbpException("This unit of work has already been initialized.");
}
Options = _defaultOptions.Normalize(options.Clone());
@ -84,7 +84,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
public virtual void Reserve(string reservationName)
{
Check.NotNull(reservationName, nameof(reservationName));
Check.NotNullOrWhiteSpace(reservationName, nameof(reservationName));
ReservationName = reservationName;
IsReserved = true;
@ -188,12 +188,12 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
public virtual void AddDatabaseApi(string key, IDatabaseApi api)
{
Check.NotNull(key, nameof(key));
Check.NotNullOrWhiteSpace(key, nameof(key));
Check.NotNull(api, nameof(api));
if (_databaseApis.ContainsKey(key))
{
throw new AbpException("There is already a database API in this unit of work with given key.");
throw new AbpException("This unit of work already contains a database API for the given key.");
}
_databaseApis.Add(key, api);
@ -201,7 +201,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
public virtual IDatabaseApi GetOrAddDatabaseApi(string key, Func<IDatabaseApi> factory)
{
Check.NotNull(key, nameof(key));
Check.NotNullOrWhiteSpace(key, nameof(key));
Check.NotNull(factory, nameof(factory));
return _databaseApis.GetOrAdd(key, factory);
@ -209,19 +209,19 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
public virtual ITransactionApi FindTransactionApi(string key)
{
Check.NotNull(key, nameof(key));
Check.NotNullOrWhiteSpace(key, nameof(key));
return _transactionApis.GetOrDefault(key);
}
public virtual void AddTransactionApi(string key, ITransactionApi api)
{
Check.NotNull(key, nameof(key));
Check.NotNullOrWhiteSpace(key, nameof(key));
Check.NotNull(api, nameof(api));
if (_transactionApis.ContainsKey(key))
{
throw new AbpException("There is already a transaction API in this unit of work with given key.");
throw new AbpException("This unit of work already contains a transaction API for the given key.");
}
_transactionApis.Add(key, api);
@ -229,7 +229,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
public virtual ITransactionApi GetOrAddTransactionApi(string key, Func<ITransactionApi> factory)
{
Check.NotNull(key, nameof(key));
Check.NotNullOrWhiteSpace(key, nameof(key));
Check.NotNull(factory, nameof(factory));
return _transactionApis.GetOrAdd(key, factory);
@ -332,7 +332,7 @@ public class UnitOfWork : IUnitOfWork, ITransientDependency
{
if (IsCompleted || _isCompleting)
{
throw new AbpException("Complete is called before!");
throw new AbpException("Completion has already been requested for this unit of work.");
}
}

17
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProviderController.cs

@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Tracing;
namespace Volo.Abp.AspNetCore.CorrelationIdProvider;
[Route("api/correlation")]
public class CorrelationIdProviderController : AbpController
{
[HttpGet]
[Route("get")]
public string Get()
{
return this.HttpContext.RequestServices.GetRequiredService<ICorrelationIdProvider>().Get();
}
}

48
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/CorrelationIdProvider/CorrelationIdProvider_Tests.cs

@ -0,0 +1,48 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.AspNetCore.Mvc;
using Xunit;
namespace Volo.Abp.AspNetCore.CorrelationIdProvider;
public class CorrelationIdProvider_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task Test()
{
// Test AbpCorrelationIdMiddleware without X-Correlation-Id header
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/correlation/404"))
{
var response = await Client.SendAsync(requestMessage);
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
response.Headers.ShouldContain(x => x.Key == "X-Correlation-Id" && x.Value.First() != null);
}
var correlationId = Guid.NewGuid().ToString("N");
// Test AbpCorrelationIdMiddleware
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/correlation/404"))
{
requestMessage.Headers.Add("X-Correlation-Id", correlationId);
var response = await Client.SendAsync(requestMessage);
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
response.Headers.ShouldContain(x => x.Key == "X-Correlation-Id" && x.Value.First() == correlationId);
}
// Test AspNetCoreCorrelationIdProvider
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/correlation/get"))
{
requestMessage.Headers.Add("X-Correlation-Id", correlationId);
var response = await Client.SendAsync(requestMessage);
response.StatusCode.ShouldBe(HttpStatusCode.OK);
(await response.Content.ReadAsStringAsync()).ShouldBe(correlationId);
}
}
}

33
framework/test/Volo.Abp.Core.Tests/Volo/Abp/CorrelationIdProvider/CorrelationIdProvider_Tests.cs

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Tracing;
using Xunit;
namespace Volo.Abp.CorrelationIdProvider;
public class CorrelationIdProvider_Tests
{
[Fact]
public async Task Test()
{
using (var application = await AbpApplicationFactory.CreateAsync<IndependentEmptyModule>())
{
await application.InitializeAsync();
var correlationIdProvider = application.ServiceProvider.GetRequiredService<ICorrelationIdProvider>();
correlationIdProvider.Get().ShouldBeNull();
var correlationId = Guid.NewGuid().ToString("N");
using (correlationIdProvider.Change(correlationId))
{
correlationIdProvider.Get().ShouldBe(correlationId);
}
correlationIdProvider.Get().ShouldBeNull();
}
}
}

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/AbpCompiledViewProviderOptions_Tests.cs

@ -38,7 +38,7 @@ public class AbpCompiledViewProviderOptions_Tests : TemplateDefinitionTests<Razo
[Fact]
public async Task Custom_TemplateReferences_Test()
{
var templateDefinition = _templateDefinitionManager.GetOrNull(RazorTestTemplates.TestTemplate);
var templateDefinition = await _templateDefinitionManager.GetOrNullAsync(RazorTestTemplates.TestTemplate);
var assembly = await _compiledViewProvider.GetAssemblyAsync(templateDefinition);

19
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateContentFileProvider_Tests.cs

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.TextTemplating.VirtualFiles;
using Xunit;
namespace Volo.Abp.TextTemplating.Razor;
public class RazorTemplateContentFileProvider_Tests : TemplateContentFileProvider_Tests<RazorTextTemplatingTestModule>
{
[Fact]
public async Task GetRazorFilesAsync()
{
var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail);
var files = await TemplateContentFileProvider.GetFilesAsync(definition);
files.Count.ShouldBe(2);
files.ShouldContain(x => x.FileName == "en.cshtml" && x.FileContent.Contains("Welcome @Model.Name to the abp.io!"));
files.ShouldContain(x => x.FileName == "tr.cshtml" && x.FileContent.Contains("Merhaba @Model.Name, abp.io'ya hoşgeldiniz!"));
}
}

19
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ScribanTemplateContentFileProvider_Tests.cs

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.TextTemplating.VirtualFiles;
using Xunit;
namespace Volo.Abp.TextTemplating.Scriban.SampleTemplates;
public class ScribanTemplateContentFileProvider_Tests : TemplateContentFileProvider_Tests<ScribanTextTemplatingTestModule>
{
[Fact]
public async Task GetScribanFilesAsync()
{
var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail);
var files = await TemplateContentFileProvider.GetFilesAsync(definition);
files.Count.ShouldBe(2);
files.ShouldContain(x => x.FileName == "en.tpl" && x.FileContent.Contains("Welcome {{model.name}} to the abp.io!"));
files.ShouldContain(x => x.FileName == "tr.tpl" && x.FileContent.Contains("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"));
}
}

17
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs

@ -1,4 +1,5 @@
using Shouldly;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;
@ -15,28 +16,28 @@ public abstract class TemplateDefinitionTests<TStartupModule> : AbpTextTemplatin
}
[Fact]
public void Should_Retrieve_Template_Definition_By_Name()
public async Task Should_Retrieve_Template_Definition_By_Name()
{
var welcomeEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail);
var welcomeEmailTemplate = await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail);
welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail);
welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse();
var forgotPasswordEmailTemplate = TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail);
var forgotPasswordEmailTemplate = await TemplateDefinitionManager.GetAsync(TestTemplates.ForgotPasswordEmail);
forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail);
forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue();
}
[Fact]
public void Should_Get_Null_If_Template_Not_Found()
public async Task Should_Get_Null_If_Template_Not_Found()
{
var definition = TemplateDefinitionManager.GetOrNull("undefined-template");
var definition = await TemplateDefinitionManager.GetOrNullAsync("undefined-template");
definition.ShouldBeNull();
}
[Fact]
public void Should_Retrieve_All_Template_Definitions()
public async Task Should_Retrieve_All_Template_Definitions()
{
var definitions = TemplateDefinitionManager.GetAll();
var definitions = await TemplateDefinitionManager.GetAllAsync();
definitions.Count.ShouldBeGreaterThan(1);
}
}

2
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory_Tests.cs

@ -24,7 +24,7 @@ public abstract class LocalizedTemplateContentReaderFactory_Tests<TStartupModule
[Fact]
public async Task Create_Should_Work_With_PhysicalFileProvider()
{
var reader = await LocalizedTemplateContentReaderFactory.CreateAsync(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail));
var reader = await LocalizedTemplateContentReaderFactory.CreateAsync(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail));
reader.GetContentOrNull("en").ShouldBe(WelcomeEmailEnglishContent);
reader.GetContentOrNull("tr").ShouldBe(WelcomeEmailTurkishContent);

29
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/TemplateContentFileProvider_Tests.cs

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;
namespace Volo.Abp.TextTemplating.VirtualFiles;
public abstract class TemplateContentFileProvider_Tests<TStartupModule> : AbpTextTemplatingTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
protected readonly TemplateContentFileProvider TemplateContentFileProvider;
protected readonly ITemplateDefinitionManager TemplateDefinitionManager;
protected TemplateContentFileProvider_Tests()
{
TemplateContentFileProvider = GetRequiredService<TemplateContentFileProvider>();
TemplateDefinitionManager = GetRequiredService<ITemplateDefinitionManager>();
}
[Fact]
public async Task GetFilesAsync()
{
var definition = await TemplateDefinitionManager.GetAsync(TestTemplates.HybridTemplateScriban);
var files = await TemplateContentFileProvider.GetFilesAsync(definition);
files.Count.ShouldBe(1);
files.ShouldContain(x => x.FileName == "TestScribanTemplate.tpl" && x.FileContent == "Hello {{model.name}}, {{L \"HowAreYou\" }}");
}
}

6
framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs

@ -24,13 +24,13 @@ public abstract class VirtualFileTemplateContributor_Tests<TStartupModule> : Abp
public async Task Should_Get_Localized_Content_By_Culture()
{
(await VirtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail),
new TemplateContentContributorContext(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail),
ServiceProvider,
"en")))
.ShouldBe(WelcomeEmailEnglishContent);
(await VirtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(TemplateDefinitionManager.Get(TestTemplates.WelcomeEmail),
new TemplateContentContributorContext(await TemplateDefinitionManager.GetAsync(TestTemplates.WelcomeEmail),
ServiceProvider,
"tr")))
.ShouldBe(WelcomeEmailTurkishContent);
@ -41,7 +41,7 @@ public abstract class VirtualFileTemplateContributor_Tests<TStartupModule> : Abp
{
(await VirtualFileTemplateContentContributor.GetOrNullAsync(
new TemplateContentContributorContext(
TemplateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail),
await TemplateDefinitionManager.GetAsync(TestTemplates.ForgotPasswordEmail),
ServiceProvider,
null)))
.ShouldBe(ForgotPasswordEmailEnglishContent);

6
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo.Abp.OpenIddict.AspNetCore.csproj

@ -20,8 +20,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="4.4.0" />
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="4.4.0" />
<PackageReference Include="OpenIddict.Validation.ServerIntegration" Version="4.4.0" />
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="$(OpenIddictPackageVersion)" />
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="$(OpenIddictPackageVersion)" />
<PackageReference Include="OpenIddict.Validation.ServerIntegration" Version="$(OpenIddictPackageVersion)" />
</ItemGroup>
</Project>

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain.Shared/Volo.Abp.OpenIddict.Domain.Shared.csproj

@ -14,7 +14,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenIddict.Abstractions" Version="4.4.0" />
<PackageReference Include="OpenIddict.Abstractions" Version="$(OpenIddictPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftPackageVersion)" />
</ItemGroup>

2
modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo.Abp.OpenIddict.Domain.csproj

@ -17,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenIddict.Core" Version="4.4.0" />
<PackageReference Include="OpenIddict.Core" Version="$(OpenIddictPackageVersion)" />
</ItemGroup>
</Project>

2
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html

@ -1,4 +1,4 @@
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn" validateOnSubmit>
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn" validateOnSubmit class="abp-md-form">
<div *ngIf="!hideCurrentPassword" class="mb-3 form-group">
<label for="current-password" class="form-label">{{
'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization

4
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html

@ -1,6 +1,6 @@
<div id="AbpContentToolbar"></div>
<div class="card border-0 shadow-sm min-h-400" [abpLoading]="!(profile$ | async)?.userName">
.
<div class="card border-0 shadow-sm min-h-400" [abpLoading]="!(profile$ | async)?.userName" >
<div class="card-body">
<div class="row">
<div class="col-12 col-md-3">

2
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html

@ -1,4 +1,4 @@
<form [formGroup]="form" *ngIf="form" (ngSubmit)="submit()" validateOnSubmit>
<form [formGroup]="form" *ngIf="form" (ngSubmit)="submit()" validateOnSubmit class="abp-md-form">
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
<abp-button

2
npm/ng-packs/packages/setting-management/config/src/lib/components/email-setting-group/email-setting-group.component.html

@ -2,7 +2,7 @@
<hr class="my-3" />
<form *ngIf="form" [formGroup]="form" (ngSubmit)="submit()" [validateOnSubmit]="true">
<form *ngIf="form" [formGroup]="form" class="abp-md-form" (ngSubmit)="submit()" [validateOnSubmit]="true">
<div class="mb-3 form-group">
<label class="form-label"
>{{ 'AbpSettingManagement::DefaultFromDisplayName' | abpLocalization

4
npm/ng-packs/packages/theme-basic/src/lib/constants/styles.ts

@ -147,4 +147,8 @@ background-color: rgba(0, 0, 0, 0.6);
background-color: transparent;
padding: 0.27rem;
}
.abp-md-form {
max-width: 540px;
}
`;

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Data/MyProjectNameEFCoreDbSchemaMigrator.cs

@ -15,7 +15,7 @@ public class MyProjectNameEFCoreDbSchemaMigrator : ITransientDependency
public async Task MigrateAsync()
{
/* We intentionally resolving the MyProjectNameDbContext
/* We intentionally resolve the MyProjectNameDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Data/MyProjectNameEFCoreDbSchemaMigrator.cs

@ -15,7 +15,7 @@ public class MyProjectNameEFCoreDbSchemaMigrator : ITransientDependency
public async Task MigrateAsync()
{
/* We intentionally resolving the MyProjectNameDbContext
/* We intentionally resolve the MyProjectNameDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/Data/MyProjectNameEFCoreDbSchemaMigrator.cs

@ -15,7 +15,7 @@ public class MyProjectNameEFCoreDbSchemaMigrator : ITransientDependency
public async Task MigrateAsync()
{
/* We intentionally resolving the MyProjectNameDbContext
/* We intentionally resolve the MyProjectNameDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Data/MyProjectNameEFCoreDbSchemaMigrator.cs

@ -15,7 +15,7 @@ public class MyProjectNameEFCoreDbSchemaMigrator : ITransientDependency
public async Task MigrateAsync()
{
/* We intentionally resolving the MyProjectNameDbContext
/* We intentionally resolve the MyProjectNameDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.

2
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreMyProjectNameDbSchemaMigrator.cs

@ -20,7 +20,7 @@ public class EntityFrameworkCoreMyProjectNameDbSchemaMigrator
public async Task MigrateAsync()
{
/* We intentionally resolving the MyProjectNameDbContext
/* We intentionally resolve the MyProjectNameDbContext
* from IServiceProvider (instead of directly injecting it)
* to properly get the connection string of the current tenant in the
* current scope.

Loading…
Cancel
Save