From bf3a7d4ccbda3ed27389eed334ca26bdda8f97c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AC=BC=E8=B0=B7=E5=AD=90?= <358683537@qq.com> Date: Wed, 23 Feb 2022 12:01:13 +0800 Subject: [PATCH 01/10] tag: 0.3.0-preview.1 (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update readme * Merge branch 'docs/contrib' of... * fix: Fix the problem of failing to get context after enabling soft delete * Configuration and UnitTest * chore: support net6.0 * Doc/contrib * Feature/optimize * chore: add package CI * chore: update library package * chore: add Codecov * chore: update codecov * chore: update codecov * docs: add codecov badge * chore: codecov ignore tests * chore: update codecov * chore: update codecov * Update packge.yml * Feature/ci * ๐Ÿ†• feat(minimal-apis): Improve MapGet, MapPost, MapPut and MapDelete with BaseUri prop * ๐Ÿ› fix: Combine baseUri and customUri * Update .gitlab-ci.yml * ๆ›ดๆ–ฐ.gitlab-ci.ymlๆ–‡ไปถ * Delete .gitlab-ci.yml * chore: change fileName * chore: update dapr library package * chore: change readme * chore: Added a solution to the failure to obtain the Event relationship chain * Fix/event * add ghpackageconfig * Update package_push_github.yml * Update package_push_nuget.org.yml * Update nuget.config * Update nuget.config * Update package_push_nuget.org.yml * Update package_push_github.yml * Update nuget.config * chore: Modify the introduction of Uow usage documentation (#3) * chore: Modify the introduction of Uow usage documentation * chore: Remove invalid comments * Update package_push_github.yml * Update nuget.config * feat:event bus * chore: Support local message retry * chore: add retry record * chore: Change the current time to Utc time * chore: Optimize background tasks * chore: Increase local message retry * chore: Add local queue to support short retry * chore: add retry retry Policy Documentation * chore: Upgrade the base library * chore: 1. Adjust EventBus to automatically execute savechange 2. UoW supports Dispose 3. Simplify the writing of Event, Command, Query, IntegrationEvent, etc. * chore: Open transactions are controlled by Repository * chore: adjust UnitOfWork and localmessage * chore: add EventBus doc * chore: Add local message table log * chore: Adjust retry configuration * chore: code review modification * chore: Get current time support modification * chore: Change the file name ConfigurationAPIClient -> ConfigurationApiClient * chore: Change the file name ConfigurationAPIClient -> ConfigurationApiClient Change the file name ConfigurationAPIManage -> ConfigurationApiManage EntityState overload * chore: Change the file name ConfigurationAPIClient -> ConfigurationApiClient Change the file name ConfigurationAPIManage -> ConfigurationApiManage EntityState overload * chore: Update MASA.BuildingBlocks.DDD.Domain package version * chore: add JsonIgnore and adjust unittest * chore: code review modification * chore: Adjust the writing method of CheckAndOpenTransaction to simplify nested if * chore: Change the parameter description, change Task.Delay to Thread.Sleep * fix: Fixed an error in the PublishQueueAsync method in the IDomainEventBus * fix: Fixed an error in the PublishQueueAsync method in the IDomainEventBus class * chore: remove using by class * Squashed 'src/MASA.BuildingBlocks/' content from commit b6a2d36 git-subtree-dir: src/MASA.BuildingBlocks git-subtree-split: b6a2d366dae89cef24d452ae2ad4d4debe252ae6 * update src * add buildingblock * update action * Delete package_push_github.yml * Update package_push_nuget.org.yml * refactor: project references building blocks * chore: Support local message retry * chore: add retry record * chore: Change the current time to Utc time * chore: Optimize background tasks * chore: Increase local message retry * chore: Add local queue to support short retry * chore: add retry retry Policy Documentation * chore: Upgrade the base library * chore: 1. Adjust EventBus to automatically execute savechange 2. UoW supports Dispose 3. Simplify the writing of Event, Command, Query, IntegrationEvent, etc. * chore: Open transactions are controlled by Repository * chore: adjust UnitOfWork and localmessage * chore: add EventBus doc * chore: Add local message table log * chore: Adjust retry configuration * chore: code review modification * chore: Get current time support modification * chore: Change the file name ConfigurationAPIClient -> ConfigurationApiClient * chore: Change the file name ConfigurationAPIClient -> ConfigurationApiClient Change the file name ConfigurationAPIManage -> ConfigurationApiManage EntityState overload * chore: Change the file name ConfigurationAPIClient -> ConfigurationApiClient Change the file name ConfigurationAPIManage -> ConfigurationApiManage EntityState overload * chore: Update MASA.BuildingBlocks.DDD.Domain package version * chore: add JsonIgnore and adjust unittest * chore: code review modification * chore: Adjust the writing method of CheckAndOpenTransaction to simplify nested if * chore: Change the parameter description, change Task.Delay to Thread.Sleep * Update package_push_github.yml * chore: update library package and filter the local failure message just executed * chore: update library package * Update package_push_github.yml * Update package_push_github.yml * chore: ILogger changed to optional * chore: add Logging by integrationEventBus * chore: Handle background local message tasks * refactor: Handling null exceptions, warnings, and Logger changed to not requiredHandling null exceptions, warnings, and Logger changed to not required * chore: update library package * chore: adjust DelayAsync * chore: Replacement interval in seconds * chore: delete summodule * chore: add MASA.BuildingBlocks submodules * chore: Change parameter remarks * chore: Adjust package references * chore: remove MASA.BuildingBlock package * Update package_push_nuget.org.yml Co-authored-by: zhenlei520 Co-authored-by: zhenlei520 Co-authored-by: ๆœฑๅต˜ Co-authored-by: capdiem Co-authored-by: ๆ›นๅฐคๅ…ˆ Co-authored-by: zhenlei520 Co-authored-by: ็Ž‹่พพ Co-authored-by: PollosD <55781685+PollosD@users.noreply.github.com> --- .github/workflows/package_push_nuget.org.yml | 42 + .gitlab-ci.yml | 32 - .gitmodules | 3 + Directory.Build.props | 24 + LICENSE => LICENSE.txt | 0 MASA.Contrib.sln | 345 ++++++-- NuGet.Config | 6 + README.md | 73 +- README.zh-CN.md | 74 +- docs/LoadEvent.md | 10 + nuget.config | 13 - packageIcon.png | Bin 0 -> 9523 bytes .../ConfigurationApiClient.cs | 158 ++++ .../ConfigurationApiManage.cs | 28 + .../Internal/ConfigFormats.cs | 8 + .../Internal/ConfigurationAPIBase.cs | 37 + .../Internal/Constants.cs | 12 + .../Internal/DccConfigurationRepository.cs | 92 ++ .../Internal/DccFactory.cs | 20 + .../Internal/Model/Property.cs | 8 + .../Internal/Model/PublishRelease.cs | 8 + .../Parser/JsonConfigurationParser.cs | 101 +++ .../Parser/PropertyConfigurationParser.cs | 7 + .../MASA.Contrib.BasicAbility.Dcc.csproj | 23 + .../MasaConfigurationExtensions.cs | 204 +++++ .../Options/DccConfigurationOptions.cs | 13 + .../Options/DccExpandSectionOptions.cs | 9 + .../Options/DccSectionOptions.cs | 24 + .../MASA.Contrib.BasicAbility.Dcc/README.md | 164 ++++ .../README.zh-CN.md | 155 ++++ .../MASA.Contrib.BasicAbility.Dcc/_Imports.cs | 24 + src/BuildingBlocks/MASA.BuildingBlocks | 1 + .../LocalMasaConfigurationRepository.cs | 71 ++ .../MASA.Contrib.Configuration.csproj | 17 + .../MasaConfigurationBuilder.cs | 52 ++ .../MasaConfigurationExtensions.cs | 35 + .../MasaConfigurationOptions.cs | 19 + .../MasaConfigurationProvider.cs | 64 ++ .../MasaConfigurationSource.cs | 17 + .../MasaRelationOptions.cs | 33 + .../MASA.Contrib.Configuration/README.md | 135 +++ .../README.zh-CN.md | 140 +++ .../ServiceCollectionExtensions.cs | 138 +++ .../MASA.Contrib.Configuration/_Imports.cs | 15 + .../DispatcherOptionsExtensions.cs | 8 +- .../Internal/LinqExtensions.cs | 93 ++ .../ServiceCollectionRepositoryExtensions.cs | 50 +- ...SA.Contrib.DDD.Domain.Repository.EF.csproj | 13 +- .../README.md | 6 +- .../{README.zh-cn.md => README.zh-CN.md} | 10 +- .../Repository.cs | 157 +++- .../_Imports.cs | 5 +- .../MASA.Contrib.DDD.Domain/DomainEventBus.cs | 53 +- .../Events/DomainCommand.cs | 16 +- .../Events/DomainEvent.cs | 15 +- .../Events/DomainQuery.cs | 20 +- .../Events/IntegrationDomainEvent.cs | 7 +- .../Internal/InvokeBuilder.cs | 21 + .../MASA.Contrib.DDD.Domain.csproj | 9 +- .../Options/DispatcherOptions.cs | 10 +- src/DDD/MASA.Contrib.DDD.Domain/README.md | 14 +- .../{README.zh-cn.md => README.zh-CN.md} | 16 +- .../ServiceCollectionExtensions.cs | 10 +- src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs | 5 +- .../MASA.Contrib.Data.Contracts.EF.csproj | 13 +- .../MASA.Contrib.Data.Contracts.EF/README.md | 19 +- .../{README.zh-cn.md => README.zh-CN.md} | 19 +- .../ServiceCollectionExtensions.cs | 1 - .../SoftDelete/SoftDeleteSaveChangesFilter.cs | 5 +- .../TransactionSaveChangesFilter.cs | 18 - .../_Imports.cs | 2 +- .../DispatcherOptionsExtensions.cs | 30 +- .../MASA.Contrib.Data.UoW.EF.csproj | 20 + src/Data/MASA.Contrib.Data.UoW.EF/README.md | 20 + .../MASA.Contrib.Data.UoW.EF/README.zh-CN.md | 20 + .../Transaction.cs | 6 +- .../UnitOfWork.cs | 57 +- .../_Imports.cs | 5 +- .../MASA.Contrib.Data.Uow.EF.csproj | 17 - .../MASA.Contrib.Dispatcher.Events/Event.cs | 14 +- .../EventBus.cs | 28 +- .../Dispatch/DispatchRelationNetwork.cs | 8 +- .../Internal/Dispatch/Dispatcher.cs | 6 +- .../Internal/Dispatch/DispatcherBase.cs | 45 +- .../Internal/Dispatch/SagaDispatcher.cs | 20 +- .../Middleware/TransactionMiddleware.cs | 49 +- .../MASA.Contrib.Dispatcher.Events.csproj | 17 +- .../Options/DispatcherOptions.cs | 19 +- .../MASA.Contrib.Dispatcher.Events/README.md | 25 +- .../{README.zh-cn.md => README.zh-CN.md} | 27 +- .../ServiceCollectionExtensions.cs | 18 +- .../Strategies/ExecutionStrategy.cs | 10 +- .../_Imports.cs | 4 +- .../IProcessingServer.cs | 6 + .../IProcessor.cs | 13 + .../IntegrationEvent.cs | 17 +- .../IntegrationEventBus.cs | 69 +- .../IntegrationEventHostedService.cs | 20 + .../Internal/IntegrationEventLogItem.cs | 31 + .../Internal/LocalQueueProcessor.cs | 60 ++ ...b.Dispatcher.IntegrationEvents.Dapr.csproj | 15 +- .../Options/DispatcherOptions.cs | 132 ++- .../DeleteLocalQueueExpiresProcessor.cs | 24 + .../DeletePublishedExpireEventProcessor.cs | 32 + .../Processor/InfiniteLoopProcessor.cs | 35 + .../Processor/ProcessorBase.cs | 19 + .../Processor/RetryByDataProcessor.cs | 76 ++ .../Processor/RetryByLocalQueueProcessor.cs | 72 ++ .../README.md | 47 +- .../README.zh-CN.md | 105 +++ .../README.zh-cn.md | 65 -- .../Servers/DefaultHostedService.cs | 20 + .../ServiceCollectionExtensions.cs | 30 +- .../_Imports.cs | 8 +- .../DispatcherOptionsExtensions.cs | 20 +- .../IntegrationEventLogContext.cs | 9 + .../IntegrationEventLogService.cs | 127 ++- .../Internal/QueryFilterProvider.cs | 3 - ...cher.IntegrationEvents.EventLogs.EF.csproj | 12 +- .../README.md | 2 + .../{README.zh-cn.md => README.zh-CN.md} | 4 +- .../_Imports.cs | 3 + .../Commands/Command.cs | 17 +- ...MASA.Contrib.ReadWriteSpliting.CQRS.csproj | 13 +- .../Queries/Query.cs | 14 +- .../Queries/QueryHandler.cs | 11 +- .../README.md | 8 +- .../{README.zh-cn.md => README.zh-CN.md} | 10 +- .../_Imports.cs | 3 + .../MASA.Contrib.Service.MinimalAPIs.csproj | 10 +- .../README.md | 6 +- .../{README.zh-cn.md => README.zh-CN.md} | 6 +- .../ServiceBase.cs | 102 ++- .../ServiceCollectionExtensions.cs | 13 +- .../_Imports.cs | 2 + .../DccClientTest.cs | 388 +++++++++ .../DccManageTest.cs | 162 ++++ .../DccTest.cs | 799 ++++++++++++++++++ .../Internal/Common/SerializeCommon.cs | 7 + .../Internal/Config/Property.cs | 8 + .../Internal/Config/PublishRelease.cs | 8 + .../Internal/CustomTrigger.cs | 26 + .../Internal/Enum/ConfigFormats.cs | 8 + .../Internal/Model/Brands.cs | 14 + ...MASA.Contrib.BasicAbility.Dcc.Tests.csproj | 36 + .../_Imports.cs | 14 + .../appsettings.json | 23 + .../expandSections.json | 32 + .../ErrorKafkaOptions.cs | 12 + .../KafkaOptions.cs | 15 + ...iguration.ErrorSectionAutoMap.Tests.csproj | 14 + .../_Imports.cs | 2 + ...ion.MountErrorSectionAutoMap.Tests.csproj} | 6 +- .../MountSectionRedisOptions.cs | 12 + .../_Imports.cs | 2 + .../Config/RabbitMqOptions.cs | 16 + .../Config/RedisOptions.cs | 10 + .../Config/SystemOptions.cs | 14 + .../ConfigurationTest.cs | 266 ++++++ .../MASA.Contrib.Configuration.Tests.csproj | 43 + .../_Imports.cs | 10 + .../appsettings.json | 9 + .../rabbitMq.json | 7 + .../redis.json | 5 + ...n.Repository.EF.CombinedKeys.Tests.csproj} | 3 +- .../Students.cs | 28 + .../_Imports.cs | 0 .../Courses.cs | 19 + ...ository.EF.CombinedKeysNoFind.Tests.csproj | 14 + .../_Imports.cs | 1 + .../Entities/User.cs | 2 +- ...epository.EF.CustomRepository.Tests.csproj | 14 + .../Repositories/IUserRepository.cs | 4 +- .../_Imports.cs | 1 - .../Hobbies.cs | 16 + ...D.Domain.Repository.EF.Entity.Tests.csproj | 14 + .../_Imports.cs | 1 + .../BaseRepositoryTest.cs | 78 ++ .../Domain/Entities/Address.cs | 31 +- .../Domain/Entities/Orders.cs | 37 +- .../Domain/Repositories/IOrderRepository.cs | 1 + .../Infrastructure/CustomDbContext.cs | 23 + .../Options/DispatcherOptions.cs | 8 - .../Infrastructure/OrderDbContext.cs | 8 - .../Repositories/OrderRepository.cs | 19 +- ...trib.DDD.Domain.Repository.EF.Tests.csproj | 13 +- .../RepositoryTest.cs | 263 +++++- .../TestBase.cs | 22 +- .../_Imports.cs | 9 +- .../DomainEventBusTest.cs | 336 +++++--- .../DomainIntegrationEventBusTest.cs | 49 ++ .../EventHandlers/PaymentSucceededHandlers.cs | 14 - .../Events/ForgetPasswordEvent.cs | 10 + .../PaymentFailedIntegrationDomainEvent.cs | 7 +- .../Events/PaymentSucceededDomainEvent.cs | 5 +- .../PaymentSucceededIntegraionDomainEvent.cs | 6 + .../PaymentSucceededDomainEventHandller.cs | 19 + .../MASA.Contrib.DDD.Domain.Tests.csproj | 11 +- .../Repositories/UserRepository.cs | 96 --- .../Services/UserDomainService.cs | 4 +- .../MASA.Contrib.DDD.Domain.Tests/TestBase.cs | 33 - .../MASA.Contrib.DDD.Domain.Tests/_Imports.cs | 10 +- .../Domain/Entities/Courses.cs | 14 + .../Domain/Entities/Students.cs | 16 + .../Infrastructure/CustomDbContext.cs | 12 + ...ASA.Contrib.Data.Contracts.EF.Tests.csproj | 28 + .../SoftDeleteTest.cs | 117 +++ .../_Imports.cs | 10 + .../CustomerDbContext.cs | 18 +- .../MASA.Contrib.Data.UoW.EF.Tests.csproj} | 10 +- .../TestBase.cs | 17 + .../TestUnitOfWork.cs | 171 ++++ .../_Imports.cs | 4 +- .../TestBase.cs | 36 - .../TestUnitOfWork.cs | 115 --- .../Benchmarks.cs | 8 +- .../Extensions/EventHandlers/CouponHandler.cs | 4 +- .../Extensions/EventHandlers/NoticeHandler.cs | 12 +- .../Extensions/Events/ForgetPasswordEvent.cs | 8 + .../Extensions/Events/RegisterUserEvent.cs | 8 + .../Middleware/LoggingMiddleware.cs | 15 + ...tcher.Events.BenchmarkDotnet.Tests.csproj} | 7 +- .../Program.cs | 2 +- .../_Imports.cs | 5 +- .../Extensions/Events/ForgetPasswordEvent.cs | 8 - .../Extensions/Events/RegisterUserEvent.cs | 8 - .../Middleware/LoggingMiddleware.cs | 15 - .../EventHandlers/AddGoodsHandler.cs | 6 +- .../_Imports.cs | 1 + .../EventHandlers/AddCatalogHandler.cs | 2 - .../_Imports.cs | 1 + .../EventHandlers/AddBasketHandler.cs | 6 +- .../EventHandlers/UserEventHandler.cs | 4 +- ...MobileEvent.cs => BindPhoneNumberEvent.cs} | 4 +- ...cher.Events.OnlyCancelHandler.Tests.csproj | 2 +- .../_Imports.cs | 1 + .../EventHandlers/EditCategoryHandler.cs | 10 +- ...tcher.Events.OrderEqualBySaga.Tests.csproj | 2 +- .../_Imports.cs | 1 + .../OrderStockConfirmedHandler.cs | 8 +- ...ts.OrderLessThanZeroByFeature.Tests.csproj | 2 +- .../_Imports.cs | 1 + .../EventHandlers/EditGoodsHandler.cs | 8 +- .../_Imports.cs | 1 + .../AssemblyResolutionTests.cs | 6 +- .../ChoreTest.cs | 6 +- .../ChangePasswordEventHandler.cs | 8 +- .../EventHandlers/ShipOrderEventHandler.cs | 16 +- .../EventHandlers/TransferEventHandler.cs | 8 +- .../Events/DeductionMoneyEvent.cs | 2 +- .../Events/IncreaseMoneyEvent.cs | 2 +- .../OrderPaymentFailedIntegrationEvent.cs | 2 +- .../FeaturesTest.cs | 50 +- ...ASA.Contrib.Dispatcher.Events.Tests.csproj | 7 +- .../Middleware/LoggingMiddleware.cs | 6 +- .../SagaTest.cs | 12 +- .../TestBase.cs | 4 +- .../_Imports.cs | 3 +- .../Events/ForgetPasswordEvent.cs | 8 - .../Events/RegisterUserIntegrationEvent.cs | 10 + .../IntegrationEventBusTest.cs | 372 +++++--- ...atcher.IntegrationEvents.Dapr.Tests.csproj | 6 +- .../RegisterServicesBusTest.cs | 15 - .../TestBase.cs | 86 -- .../_Imports.cs | 4 +- .../Events/IntegrationEvent.cs | 2 +- .../IntegrationEventLogServiceTest.cs | 118 +-- ...ntegrationEvents.EventLogs.EF.Tests.csproj | 8 +- .../TestBase.cs | 4 - .../_Imports.cs | 6 +- .../Commands/CreateProductionCommand.cs | 8 + .../CqrsTest.cs | 51 ++ .../CreateProductionCommandHandler.cs | 24 + ...ontrib.ReadWriteSpliting.CQRS.Tests.csproj | 27 + .../ProductionQueryHandler.cs | 18 + .../Queries/ProductionItemQuery.cs | 8 + .../_Imports.cs | 8 + ...A.Contrib.Service.MinimalAPIs.Tests.csproj | 26 + .../MinimalAPITest.cs | 48 ++ .../Services/CustomService.cs | 13 + .../_Imports.cs | 4 + ...Contribs.DDD.Domain.Entities.Tests.csproj} | 0 .../Users.cs | 7 + .../_Imports.cs | 1 + .../MASA.Contribs.DDD.Domain.Entities/User.cs | 7 - .../_Imports.cs | 0 286 files changed, 8025 insertions(+), 1746 deletions(-) create mode 100644 .github/workflows/package_push_nuget.org.yml delete mode 100644 .gitlab-ci.yml create mode 100644 .gitmodules create mode 100644 Directory.Build.props rename LICENSE => LICENSE.txt (100%) create mode 100644 NuGet.Config create mode 100644 docs/LoadEvent.md delete mode 100644 nuget.config create mode 100644 packageIcon.png create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiClient.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md create mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs create mode 160000 src/BuildingBlocks/MASA.BuildingBlocks create mode 100644 src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj create mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/README.md create mode 100644 src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md create mode 100644 src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs create mode 100644 src/Configuration/MASA.Contrib.Configuration/_Imports.cs create mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs rename src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/{README.zh-cn.md => README.zh-CN.md} (88%) create mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs rename src/DDD/MASA.Contrib.DDD.Domain/{README.zh-cn.md => README.zh-CN.md} (89%) rename src/Data/MASA.Contrib.Data.Contracts.EF/{README.zh-cn.md => README.zh-CN.md} (51%) delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/TransactionSaveChangesFilter.cs rename src/Data/{MASA.Contrib.Data.Uow.EF => MASA.Contrib.Data.UoW.EF}/DispatcherOptionsExtensions.cs (50%) create mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj create mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/README.md create mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md rename src/Data/{MASA.Contrib.Data.Uow.EF => MASA.Contrib.Data.UoW.EF}/Transaction.cs (52%) rename src/Data/{MASA.Contrib.Data.Uow.EF => MASA.Contrib.Data.UoW.EF}/UnitOfWork.cs (53%) rename src/Data/{MASA.Contrib.Data.Uow.EF => MASA.Contrib.Data.UoW.EF}/_Imports.cs (67%) delete mode 100644 src/Data/MASA.Contrib.Data.Uow.EF/MASA.Contrib.Data.Uow.EF.csproj rename src/Dispatcher/MASA.Contrib.Dispatcher.Events/{README.zh-cn.md => README.zh-CN.md} (68%) create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-cn.md create mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs rename src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/{README.zh-cn.md => README.zh-CN.md} (94%) rename src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/{README.zh-cn.md => README.zh-CN.md} (89%) rename src/Service/MASA.Contrib.Service.MinimalAPIs/{README.zh-cn.md => README.zh-CN.md} (85%) create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json create mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json create mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs create mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs create mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj create mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs rename test/{MASA.Contribs.DDD.Domain.Repository/MASA.Contribs.DDD.Domain.Repository.csproj => MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj} (61%) create mode 100644 test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs create mode 100644 test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs create mode 100644 test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs create mode 100644 test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs create mode 100644 test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs create mode 100644 test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs create mode 100644 test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj create mode 100644 test/MASA.Contrib.Configuration.Tests/_Imports.cs create mode 100644 test/MASA.Contrib.Configuration.Tests/appsettings.json create mode 100644 test/MASA.Contrib.Configuration.Tests/rabbitMq.json create mode 100644 test/MASA.Contrib.Configuration.Tests/redis.json rename test/{MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.csproj => MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj} (91%) create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs rename test/{MASA.Contribs.DDD.Domain.Entities => MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests}/_Imports.cs (100%) create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs rename test/{MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository => MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests}/Entities/User.cs (91%) create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj rename test/{MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository => MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests}/Repositories/IUserRepository.cs (55%) rename test/{MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository => MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests}/_Imports.cs (58%) create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Options/DispatcherOptions.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/OrderDbContext.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/EventHandlers/PaymentSucceededHandlers.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs create mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Repositories/UserRepository.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/TestBase.cs create mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs create mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs create mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs create mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj create mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs create mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs rename test/{MASA.Contrib.Data.Uow.EF.Tests => MASA.Contrib.Data.UoW.EF.Tests}/CustomerDbContext.cs (73%) rename test/{MASA.Contrib.Data.Uow.EF.Tests/MASA.Contrib.Data.Uow.EF.Tests.csproj => MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj} (75%) create mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs create mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs rename test/{MASA.Contrib.Data.Uow.EF.Tests => MASA.Contrib.Data.UoW.EF.Tests}/_Imports.cs (72%) delete mode 100644 test/MASA.Contrib.Data.Uow.EF.Tests/TestBase.cs delete mode 100644 test/MASA.Contrib.Data.Uow.EF.Tests/TestUnitOfWork.cs rename test/{MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest => MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests}/Benchmarks.cs (89%) rename test/{MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest => MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests}/Extensions/EventHandlers/CouponHandler.cs (90%) rename test/{MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest => MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests}/Extensions/EventHandlers/NoticeHandler.cs (83%) create mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs create mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs create mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs rename test/{MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.csproj => MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj} (85%) rename test/{MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest => MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests}/Program.cs (83%) rename test/{MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest => MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests}/_Imports.cs (73%) delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/ForgetPasswordEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/RegisterUserEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Middleware/LoggingMiddleware.cs rename test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/{BindMobileEvent.cs => BindPhoneNumberEvent.cs} (58%) delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/ForgetPasswordEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/RegisterServicesBusTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/TestBase.cs create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs create mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs create mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj create mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs create mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs create mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs rename test/{MASA.Contribs.DDD.Domain.Entities/MASA.Contribs.DDD.Domain.Entities.csproj => MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj} (100%) create mode 100644 test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs create mode 100644 test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs delete mode 100644 test/MASA.Contribs.DDD.Domain.Entities/User.cs delete mode 100644 test/MASA.Contribs.DDD.Domain.Repository/_Imports.cs diff --git a/.github/workflows/package_push_nuget.org.yml b/.github/workflows/package_push_nuget.org.yml new file mode 100644 index 000000000..49cc9a214 --- /dev/null +++ b/.github/workflows/package_push_nuget.org.yml @@ -0,0 +1,42 @@ +name: Package Push Nuget +on: + release: + types: [ created ] + +jobs: + package-build: + name: packeg build and push + runs-on: ubuntu-latest + steps: + - name: git pull + uses: actions/checkout@v2 + + - name: run a one-line script + run: env + + - name: setting dotnet version + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' + include-prerelease: true + - name: dependencies + - run: git clone -b main https://github.com/masastack/MASA.BuildingBlocks.git ./src/BuildingBlocks + + - name: restore + run: dotnet restore + + - name: build + run: dotnet build --no-restore /p:ContinuousIntegrationBuild=true + + - name: test + run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[*.Tests]*" + + - name: codecov + uses: codecov/codecov-action@v1 + + - name: pack + run: dotnet pack --include-symbols -p:PackageVersion=$GITHUB_REF_NAME + + - name: package push + run: dotnet nuget push "**/*.symbols.nupkg" -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 4e8ff5524..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,32 +0,0 @@ -image: mcr.microsoft.com/dotnet/sdk:6.0.100-preview.7 - -stages: - - build - - tests - - nuget - -build: - stage: build - only: - - branches - script: - - dotnet build - retry: 2 - -# tests: -# stage: tests -# only: -# - branches -# script: -# - dotnet test --collect:"XPlat Code Coverage" -# retry: 2 - -nuget: - stage: nuget - only: - - branches - script: - - dotnet build - - dotnet pack --include-symbols -p:PackageVersion=0.0.$CI_PIPELINE_ID - - dotnet nuget push "**/*.symbols.nupkg" --source gitlab - retry: 2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..5a6352e61 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/BuildingBlocks/MASA.BuildingBlocks"] + path = src/BuildingBlocks/MASA.BuildingBlocks + url = https://github.com/masastack/MASA.BuildingBlocks.git diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..a0865029c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,24 @@ + + + $(AssemblyName) + packageIcon.png + masastack + ยฉ masastack Corporation. All rights reserved. + packageIcon.png + https://github.com/masastack/MASA.Contrib + git + true + $(MSBuildThisFileDirectory) + LICENSE.txt + + + + True + + + + True + + + + diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/MASA.Contrib.sln b/MASA.Contrib.sln index 41a0d7273..02a1db545 100644 --- a/MASA.Contrib.sln +++ b/MASA.Contrib.sln @@ -40,14 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DDD", "DDD", "{21180442-A6A EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data", "Data", "{E33ADF54-4D35-49B7-BDA6-412587CA39FF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.Uow.EF", "src\Data\MASA.Contrib.Data.Uow.EF\MASA.Contrib.Data.Uow.EF.csproj", "{626631CD-4FD5-424E-A678-27653F38CA3E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.Uow.EF.Tests", "test\MASA.Contrib.Data.Uow.EF.Tests\MASA.Contrib.Data.Uow.EF.Tests.csproj", "{63BB9F58-316E-4F20-8F45-B45D28FC2476}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events", "src\Dispatcher\MASA.Contrib.Dispatcher.Events\MASA.Contrib.Dispatcher.Events.csproj", "{1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest", "test\MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest\MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.csproj", "{0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests", "test\MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests\MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj", "{D55A7D3B-9C24-4029-BC03-41B28AD11DB6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests", "test\MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests\MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj", "{89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}" @@ -88,11 +82,63 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Int EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.DDD.Domain", "MASA.Contrib.DDD.Domain", "{13EDB361-AF88-4F89-B4AB-46622BCCBC37}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contribs.DDD.Domain.Entities", "test\MASA.Contribs.DDD.Domain.Entities\MASA.Contribs.DDD.Domain.Entities.csproj", "{647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.DDD.Domain.Repository.EF", "MASA.Contrib.DDD.Domain.Repository.EF", "{880E8263-AECC-4793-BD28-7CD03650D124}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository", "test\MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository\MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.csproj", "{7A1493EC-196F-4389-A966-02E526453578}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.UoW.EF", "src\Data\MASA.Contrib.Data.UoW.EF\MASA.Contrib.Data.UoW.EF.csproj", "{1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.UoW.EF.Tests", "test\MASA.Contrib.Data.UoW.EF.Tests\MASA.Contrib.Data.UoW.EF.Tests.csproj", "{1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.BasicAbility.Dcc", "src\BasicAbility\MASA.Contrib.BasicAbility.Dcc\MASA.Contrib.BasicAbility.Dcc.csproj", "{FDF1C618-4C68-485E-A881-4FFF04EDF6E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration", "src\Configuration\MASA.Contrib.Configuration\MASA.Contrib.Configuration.csproj", "{C056C688-8FFC-42BC-B4EA-EF3808A8A12C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.ReadWriteSpliting.CQRS.Tests", "test\MASA.Contrib.ReadWriteSpliting.CQRS.Tests\MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj", "{428CDAF3-957A-4017-82EA-70737F205546}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration.Tests", "test\MASA.Contrib.Configuration.Tests\MASA.Contrib.Configuration.Tests.csproj", "{DB93B639-899D-4B2C-AF8A-47B4BC6B3776}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.BasicAbility.Dcc.Tests", "test\MASA.Contrib.BasicAbility.Dcc.Tests\MASA.Contrib.BasicAbility.Dcc.Tests.csproj", "{85BCA106-4A6F-4BEE-A748-E61A24D12DBD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.Configuration", "MASA.Contrib.Configuration", "{9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Service.MinimalAPIs.Tests", "test\MASA.Contrib.Service.MinimalAPIs.Tests\MASA.Contrib.Service.MinimalAPIs.Tests.csproj", "{A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contribs.DDD.Domain.Entities.Tests", "test\MASA.Contribs.DDD.Domain.Entities.Tests\MASA.Contribs.DDD.Domain.Entities.Tests.csproj", "{B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.Contracts.EF.Tests", "test\MASA.Contrib.Data.Contracts.EF.Tests\MASA.Contrib.Data.Contracts.EF.Tests.csproj", "{5A163042-B03A-4063-85FF-22D4C5BB5B90}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests", "test\MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests\MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj", "{84EFF9E1-6852-458F-8D57-62E3F084EA0F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests", "test\MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests\MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj", "{427822F2-7A20-4E3A-B45C-43CE855003C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj", "{99067BDF-2C6A-47F8-913D-3FF9F2A69F98}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj", "{A4DE46BD-1FA4-494B-80DA-6EB370686F17}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests\MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj", "{7909A736-6C1E-4622-9BE7-37EF0839FA05}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests\MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj", "{71E02AFA-06A0-4527-923C-6666B3D66542}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests", "test\MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests\MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj", "{1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{DC578D74-98F0-4F19-A230-CFA8DAEE0AF1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Configuration", "src\BuildingBlocks\MASA.BuildingBlocks\src\Configuration\MASA.BuildingBlocks.Configuration\MASA.BuildingBlocks.Configuration.csproj", "{374B7E56-A815-4F56-A4C2-6094B5A97EE7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Data.Contracts", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\MASA.BuildingBlocks.Data.Contracts\MASA.BuildingBlocks.Data.Contracts.csproj", "{7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Data.UoW", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\MASA.BuildingBlocks.Data.UoW\MASA.BuildingBlocks.Data.UoW.csproj", "{E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.DDD.Domain", "src\BuildingBlocks\MASA.BuildingBlocks\src\DDD\MASA.BuildingBlocks.DDD.Domain\MASA.BuildingBlocks.DDD.Domain.csproj", "{2971858E-2CCA-4688-B8CA-84F130AD5AA9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Dispatcher.Events", "src\BuildingBlocks\MASA.BuildingBlocks\src\Dispatcher\MASA.BuildingBlocks.Dispatcher.Events\MASA.BuildingBlocks.Dispatcher.Events.csproj", "{2503F67B-63BB-4364-8B31-1DD3049C92B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Dispatcher.IntegrationEvents", "src\BuildingBlocks\MASA.BuildingBlocks\src\Dispatcher\MASA.BuildingBlocks.Dispatcher.IntegrationEvents\MASA.BuildingBlocks.Dispatcher.IntegrationEvents.csproj", "{88BC8170-9123-48C0-A914-11D3CE805196}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.ReadWriteSpliting.CQRS", "src\BuildingBlocks\MASA.BuildingBlocks\src\ReadWriteSpliting\MASA.BuildingBlocks.ReadWriteSpliting.CQRS\MASA.BuildingBlocks.ReadWriteSpliting.CQRS.csproj", "{316B1D0A-9CF7-4E5C-A39A-8A389B075A19}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.SearchEngine.AutoComplete", "src\BuildingBlocks\MASA.BuildingBlocks\src\SearchEngine\MASA.BuildingBlocks.SearchEngine.AutoComplete\MASA.BuildingBlocks.SearchEngine.AutoComplete.csproj", "{2F4986D6-3F56-4C05-8A1D-399594F96093}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Service.MinimalAPIs", "src\BuildingBlocks\MASA.BuildingBlocks\src\Service\MASA.BuildingBlocks.Service.MinimalAPIs\MASA.BuildingBlocks.Service.MinimalAPIs.csproj", "{E72E105D-B15F-4D69-9A13-CAA49D4889D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -110,22 +156,6 @@ Global {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|Any CPU.Build.0 = Release|Any CPU {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|x64.ActiveCfg = Release|Any CPU {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|x64.Build.0 = Release|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Debug|x64.ActiveCfg = Debug|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Debug|x64.Build.0 = Debug|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Release|Any CPU.Build.0 = Release|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Release|x64.ActiveCfg = Release|Any CPU - {626631CD-4FD5-424E-A678-27653F38CA3E}.Release|x64.Build.0 = Release|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Debug|x64.ActiveCfg = Debug|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Debug|x64.Build.0 = Debug|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Release|Any CPU.Build.0 = Release|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Release|x64.ActiveCfg = Release|Any CPU - {63BB9F58-316E-4F20-8F45-B45D28FC2476}.Release|x64.Build.0 = Release|Any CPU {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -134,14 +164,6 @@ Global {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|Any CPU.Build.0 = Release|Any CPU {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|x64.ActiveCfg = Release|Any CPU {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|x64.Build.0 = Release|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Debug|x64.ActiveCfg = Debug|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Debug|x64.Build.0 = Debug|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Release|Any CPU.Build.0 = Release|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Release|x64.ActiveCfg = Release|Any CPU - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6}.Release|x64.Build.0 = Release|Any CPU {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -294,22 +316,214 @@ Global {761C3313-A669-465F-A384-9E118FCE4F89}.Release|Any CPU.Build.0 = Release|Any CPU {761C3313-A669-465F-A384-9E118FCE4F89}.Release|x64.ActiveCfg = Release|Any CPU {761C3313-A669-465F-A384-9E118FCE4F89}.Release|x64.Build.0 = Release|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Debug|Any CPU.Build.0 = Debug|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Debug|x64.ActiveCfg = Debug|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Debug|x64.Build.0 = Debug|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Release|Any CPU.ActiveCfg = Release|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Release|Any CPU.Build.0 = Release|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Release|x64.ActiveCfg = Release|Any CPU - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32}.Release|x64.Build.0 = Release|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Debug|x64.ActiveCfg = Debug|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Debug|x64.Build.0 = Debug|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Release|Any CPU.Build.0 = Release|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Release|x64.ActiveCfg = Release|Any CPU - {7A1493EC-196F-4389-A966-02E526453578}.Release|x64.Build.0 = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|x64.Build.0 = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|Any CPU.Build.0 = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|x64.ActiveCfg = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|x64.Build.0 = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|x64.ActiveCfg = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|x64.Build.0 = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|Any CPU.Build.0 = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|x64.ActiveCfg = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|x64.Build.0 = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|x64.ActiveCfg = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|x64.Build.0 = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|Any CPU.Build.0 = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|x64.ActiveCfg = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|x64.Build.0 = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|x64.Build.0 = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|Any CPU.Build.0 = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|x64.ActiveCfg = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|x64.Build.0 = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|Any CPU.Build.0 = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|x64.ActiveCfg = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|x64.Build.0 = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|Any CPU.ActiveCfg = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|Any CPU.Build.0 = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|x64.ActiveCfg = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|x64.Build.0 = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|x64.Build.0 = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|Any CPU.Build.0 = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|x64.ActiveCfg = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|x64.Build.0 = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|x64.ActiveCfg = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|x64.Build.0 = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|Any CPU.Build.0 = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|x64.ActiveCfg = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|x64.Build.0 = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|x64.Build.0 = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|Any CPU.Build.0 = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|x64.ActiveCfg = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|x64.Build.0 = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|x64.ActiveCfg = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|x64.Build.0 = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|Any CPU.Build.0 = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|x64.ActiveCfg = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|x64.Build.0 = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|x64.ActiveCfg = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|x64.Build.0 = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|Any CPU.Build.0 = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|x64.ActiveCfg = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|x64.Build.0 = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|x64.ActiveCfg = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|x64.Build.0 = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|Any CPU.Build.0 = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|x64.ActiveCfg = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|x64.Build.0 = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|x64.Build.0 = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|Any CPU.Build.0 = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|x64.ActiveCfg = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|x64.Build.0 = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|x64.ActiveCfg = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|x64.Build.0 = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|Any CPU.Build.0 = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|x64.ActiveCfg = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|x64.Build.0 = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|x64.Build.0 = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|Any CPU.Build.0 = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|x64.ActiveCfg = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|x64.Build.0 = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|x64.ActiveCfg = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|x64.Build.0 = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|Any CPU.Build.0 = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|x64.ActiveCfg = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|x64.Build.0 = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|x64.ActiveCfg = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|x64.Build.0 = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|Any CPU.Build.0 = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|x64.ActiveCfg = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|x64.Build.0 = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|x64.Build.0 = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|Any CPU.Build.0 = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|x64.ActiveCfg = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|x64.Build.0 = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|x64.ActiveCfg = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|x64.Build.0 = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|Any CPU.Build.0 = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|x64.ActiveCfg = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|x64.Build.0 = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|x64.Build.0 = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|Any CPU.Build.0 = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|x64.ActiveCfg = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|x64.Build.0 = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|x64.Build.0 = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|Any CPU.Build.0 = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|x64.ActiveCfg = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|x64.Build.0 = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|x64.ActiveCfg = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|x64.Build.0 = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|Any CPU.Build.0 = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|x64.ActiveCfg = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|x64.Build.0 = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|x64.Build.0 = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|Any CPU.Build.0 = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|x64.ActiveCfg = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|x64.Build.0 = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|x64.ActiveCfg = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|x64.Build.0 = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|Any CPU.Build.0 = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|x64.ActiveCfg = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|x64.Build.0 = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|x64.ActiveCfg = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|x64.Build.0 = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|Any CPU.Build.0 = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|x64.ActiveCfg = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|x64.Build.0 = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|x64.Build.0 = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|Any CPU.Build.0 = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|x64.ActiveCfg = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|x64.Build.0 = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|x64.Build.0 = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|Any CPU.Build.0 = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|x64.ActiveCfg = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -329,10 +543,7 @@ Global {ED301FA5-4E70-460B-A0D4-1D79D135769F} = {593A3114-D1E0-47ED-BC37-58E08886175B} {21180442-A6A5-4239-A2AD-33FF5BB80E72} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} {E33ADF54-4D35-49B7-BDA6-412587CA39FF} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {626631CD-4FD5-424E-A678-27653F38CA3E} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF} - {63BB9F58-316E-4F20-8F45-B45D28FC2476} = {38E6C400-90C0-493E-9266-C1602E229F1B} {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C} = {FBD326D3-E59C-433E-A88E-14E179E3093D} - {0FF80D58-98D2-43E9-8EAF-7F47C31CB0B6} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} {D55A7D3B-9C24-4029-BC03-41B28AD11DB6} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} {2E172027-1B85-474E-A238-21B2DBDB895F} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} @@ -353,9 +564,35 @@ Global {E893C913-98A0-4BB3-A32B-3871BE3C5C53} = {880E8263-AECC-4793-BD28-7CD03650D124} {761C3313-A669-465F-A384-9E118FCE4F89} = {38E6C400-90C0-493E-9266-C1602E229F1B} {13EDB361-AF88-4F89-B4AB-46622BCCBC37} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {647A9FC3-4C21-4CD1-AD6A-FADFEB976E32} = {13EDB361-AF88-4F89-B4AB-46622BCCBC37} {880E8263-AECC-4793-BD28-7CD03650D124} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {7A1493EC-196F-4389-A966-02E526453578} = {880E8263-AECC-4793-BD28-7CD03650D124} + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF} + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8} = {5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48} + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C} = {59DA3D5F-9E39-4173-8C31-126967CC189F} + {428CDAF3-957A-4017-82EA-70737F205546} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826} = {13EDB361-AF88-4F89-B4AB-46622BCCBC37} + {5A163042-B03A-4063-85FF-22D4C5BB5B90} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {84EFF9E1-6852-458F-8D57-62E3F084EA0F} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} + {427822F2-7A20-4E3A-B45C-43CE855003C1} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98} = {880E8263-AECC-4793-BD28-7CD03650D124} + {A4DE46BD-1FA4-494B-80DA-6EB370686F17} = {880E8263-AECC-4793-BD28-7CD03650D124} + {7909A736-6C1E-4622-9BE7-37EF0839FA05} = {880E8263-AECC-4793-BD28-7CD03650D124} + {71E02AFA-06A0-4527-923C-6666B3D66542} = {880E8263-AECC-4793-BD28-7CD03650D124} + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {374B7E56-A815-4F56-A4C2-6094B5A97EE7} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {2971858E-2CCA-4688-B8CA-84F130AD5AA9} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {2503F67B-63BB-4364-8B31-1DD3049C92B7} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {88BC8170-9123-48C0-A914-11D3CE805196} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {2F4986D6-3F56-4C05-8A1D-399594F96093} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {E72E105D-B15F-4D69-9A13-CAA49D4889D6} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..3f0e00340 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,6 @@ +๏ปฟ + + + + + \ No newline at end of file diff --git a/README.md b/README.md index ccab56434..41afb2634 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,38 @@ [ไธญ](README.zh-CN.md) | EN +[![codecov](https://codecov.io/gh/masastack/MASA.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/MASA.Contrib) + # MASA.Contrib -MASA.Contrib is the best practice of MASA.BuildingBlocks +The purpose of MASA.Contrib is based on [MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [MASA Stack](https://github.com/masastack) and [MASA Labs](https://github.com/masalabs) projects. ## Structure ```c# MASA.Contrib -โ”‚โ”€โ”€solution items -โ”‚ โ”€โ”€ nuget.config -โ”‚โ”€โ”€src +โ”œโ”€โ”€ solution items +โ”‚ โ”œโ”€โ”€ nuget.config +โ”œโ”€โ”€ src +โ”‚ โ”œโ”€โ”€ BasicAbility +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.BasicAbility.Dcc ConfigurationAPI +โ”‚ โ”œโ”€โ”€ Configuration +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Configuration โ”‚ โ”œโ”€โ”€ Data -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.Uow.EF Unit of work -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contribs.Data.Contracts.EF Protocol EF version +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF Unit of work +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Data.Contracts.EF Protocol EF version โ”‚ โ”œโ”€โ”€ DDD -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contribs.DDD.Domain In-process and cross-process support -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contribs.DDD.Domain.Repository.EF +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain In-process and cross-process support +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF โ”‚ โ”œโ”€โ”€ Dispatcher -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events In-process event +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events In-process event โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.Dapr -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF Cross-process event +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF Cross-process event โ”‚ โ”œโ”€โ”€ ReadWriteSpliting โ”‚ โ”‚ โ””โ”€โ”€ CQRS -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.ReadWriteSpliting.CQRS CQRS +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.ReadWriteSpliting.CQRS CQRS โ”‚ โ”œโ”€โ”€ Service -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Service.MinimalAPIs Best practices for [MinimalAPI] -โ”‚โ”€โ”€test +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Service.MinimalAPIs Best practices for [MinimalAPI] +โ”œโ”€โ”€ test โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests @@ -36,17 +42,17 @@ MASA.Contrib โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.Uow.EF.Tests +โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF.Tests โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contribs.DDD.Domain.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contribs.DDD.Domain.Repository.EF.Tests +โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Tests +โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF.Tests ``` ## Feature ### 1. MinimalAPI -What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[Usage introduction](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md) +What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[Usage introduction](/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md) > Advantage๏ผš > @@ -54,7 +60,7 @@ What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates- ### 2. EventBus -[Usage introduction](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md) +[Usage introduction](/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md) > Advantage๏ผš > @@ -73,22 +79,22 @@ What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates- ### 3. CQRS -what is[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[Usage introduction](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md) +what is[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[Usage introduction](/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md) ### 4. IntegrationEventBus -Realize cross-process events based on Daprใ€‚[Usage introduction](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md) +Realize cross-process events based on Daprใ€‚[Usage introduction](/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md) > Advantage๏ผšUse the same transaction to commit the user-defined context and the log to ensure atomicity and consistency ### 5. DomainEventBus -[Usage introduction](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/DDD/MASA.Contribs.DDD.Domain/README.md) +[Usage introduction](/src/DDD/MASA.Contrib.DDD.Domain/README.md) > Advantage๏ผš > > 1. CQRS -> 2. Field Service +> 2. Domain Service > 3. Support domain events (in-process), integrated domain events (cross-process) > 4. Support the unified sending of field events after being pushed onto the stack @@ -99,35 +105,40 @@ Realize cross-process events based on Daprใ€‚[Usage introduction](http://gitlab- ### 7. Contracts.EF -Protocol based on EF implementation๏ผŒ[Usage introduction](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/Data/MASA.Contribs.Data.Contracts.EF/README.md) +Protocol based on EF implementation๏ผŒ[Usage introduction](/Data/MASA.Contrib.Data.Contracts.EF/README.md) > Advantage๏ผš > > 1. Filter deleted information when querying -> 2. Open transaction after query -> 3. Soft delete +> 2. Soft delete ```C# -Install-Package MASA.Contribs.Data.Contracts.EF +Install-Package MASA.Contrib.Data.Contracts.EF ``` ```C# -builder.Services - .AddUoW(dbOptions => +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => { dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - dbOptions.UseSoftDelete(builder.Services);//Start soft delete - }) + dbOptions.UseSoftDelete(builder.Services); + }); +}); + ``` > When the entity inherits ISoftware and is deleted, change the delete state to the modified state, and cooperate with the custom Remove operation to achieve soft deletion > Do not query the data marked as soft deleted when querying > When combined with EventBus, the transaction is opened after the first CUD, and the transaction rollback is supported when the entire Handler is abnormal. +### 8. MASA.Contrib.Configuration + +Redefine Configuration, support the management of Local and ConfigurationAPI nodes, combine IOptions and IOptionsMonitor to complete configuration acquisition and configuration update subscription [Local Usage introduction](src/Configuration/MASA.Contrib.Configuration/README.md) ใ€[Dcc Usage introduction](src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md) + ## Unit testing rules To ensure the reliability of the entire source code, the unit test coverage is at least 90% ## โ˜€๏ธ License agreement -[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/LICENSE) +[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) diff --git a/README.zh-CN.md b/README.zh-CN.md index 83518f5d6..7f3210bf1 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,32 +1,38 @@ ๏ปฟไธญ | [EN](README.md) +[![codecov](https://codecov.io/gh/masastack/MASA.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/MASA.Contrib) + # MASA.Contrib -MASA.BuildingBlocksๆœ€ไฝณๅฎž่ทต +MASA.Contribๆ˜ฏๅŸบไบŽ[MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks)ๆไพ›ๅผ€ๆ”พ, ็คพๅŒบ้ฉฑๅŠจ็š„ๅฏ้‡็”จ็ป„ไปถ๏ผŒ็”จไบŽๆž„ๅปบ็ฝ‘ๆ ผๅบ”็”จ็จ‹ๅบใ€‚่ฟ™ไบ›็ป„ไปถๅฐ†่ขซ[MASA Stack](https://github.com/masastack)ๅ’Œ[MASA Labs](https://github.com/masalabs)็ญ‰้กน็›ฎไฝฟ็”จใ€‚ ## ็ป“ๆž„ ```c# MASA.Contrib -โ”‚โ”€โ”€solution items -โ”‚ โ”€โ”€ nuget.config -โ”‚โ”€โ”€src +โ”œโ”€โ”€ solution items +โ”‚ โ”œโ”€โ”€ nuget.config +โ”œโ”€โ”€ src +โ”‚ โ”œโ”€โ”€ BasicAbility +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.BasicAbility.Dcc ConfigurationAPI +โ”‚ โ”œโ”€โ”€ Configuration +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Configuration โ”‚ โ”œโ”€โ”€ Data -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.Uow.EF ๅทฅไฝœๅ•ๅ…ƒ -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contribs.Data.Contracts.EF ่ง„็บฆEF็‰ˆ +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF ๅทฅไฝœๅ•ๅ…ƒ +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Data.Contracts.EF ่ง„็บฆEF็‰ˆ โ”‚ โ”œโ”€โ”€ DDD -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contribs.DDD.Domain ่ฟ›็จ‹ๅ†…ใ€่ทจ่ฟ›็จ‹้ƒฝๆ”ฏๆŒ -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contribs.DDD.Domain.Repository.EF +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain ่ฟ›็จ‹ๅ†…ใ€่ทจ่ฟ›็จ‹้ƒฝๆ”ฏๆŒ +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF โ”‚ โ”œโ”€โ”€ Dispatcher -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events ่ฟ›็จ‹ๅ†…ไบ‹ไปถ +โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events ่ฟ›็จ‹ๅ†…ไบ‹ไปถ โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.Dapr -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF ่ทจ่ฟ›็จ‹ไบ‹ไปถ +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF ่ทจ่ฟ›็จ‹ไบ‹ไปถ โ”‚ โ”œโ”€โ”€ ReadWriteSpliting โ”‚ โ”‚ โ””โ”€โ”€ CQRS -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.ReadWriteSpliting.CQRS CQRS +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.ReadWriteSpliting.CQRS CQRS โ”‚ โ”œโ”€โ”€ Service -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Service.MinimalAPIs MinimalAPIๆœ€ไฝณๅฎž่ทต -โ”‚โ”€โ”€test +โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Service.MinimalAPIs MinimalAPIๆœ€ไฝณๅฎž่ทต +โ”œโ”€โ”€ test โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests @@ -36,17 +42,17 @@ MASA.Contrib โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.Uow.EF.Tests +โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF.Tests โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contribs.DDD.Domain.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contribs.DDD.Domain.Repository.EF.Tests +โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Tests +โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF.Tests ``` ## ็‰นๆ€ง ### 1. MinimalAPI -ไป€ไนˆๆ˜ฏ[MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[็”จๆณ•ไป‹็ป](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-cn.md) +ไป€ไนˆๆ˜ฏ[MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[็”จๆณ•ไป‹็ป](/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md) > ไผ˜ๅŠฟ๏ผš > @@ -54,7 +60,7 @@ MASA.Contrib ### 2. EventBus -[็”จๆณ•ไป‹็ป](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-cn.md) +[็”จๆณ•ไป‹็ป](/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md) > ไผ˜ๅŠฟ๏ผš > @@ -73,23 +79,23 @@ MASA.Contrib ### 3. CQRS -ไป€ไนˆๆ˜ฏ[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[็”จๆณ•ไป‹็ป](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-cn.md) +ไป€ไนˆๆ˜ฏ[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[็”จๆณ•ไป‹็ป](/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md) ### 4. IntegrationEventBus -ๅŸบไบŽDaprๅฎž็Žฐ่ทจ่ฟ›็จ‹็š„ไบ‹ไปถใ€‚[็”จๆณ•ไป‹็ป](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-cn.md) +ๅŸบไบŽDaprๅฎž็Žฐ่ทจ่ฟ›็จ‹็š„ไบ‹ไปถใ€‚[็”จๆณ•ไป‹็ป](/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md) > ไผ˜ๅŠฟ๏ผšๅฐ†็”จๆˆท่‡ชๅฎšไน‰ไธŠไธ‹ๆ–‡ไธŽๆ—ฅๅฟ—ไฝฟ็”จๅŒไธ€ไบ‹ๅŠกๆไบค๏ผŒ็กฎไฟๅŽŸๅญๆ€งใ€ไธ€่‡ดๆ€ง ### 5. DomainEventBus -[็”จๆณ•ไป‹็ป](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/src/DDD/MASA.Contribs.DDD.Domain/README.zh-cn.md) +[็”จๆณ•ไป‹็ป](/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md) > ไผ˜ๅŠฟ๏ผš > -> 2. CQRS -> 3. ้ข†ๅŸŸๆœๅŠก -> 4. ๆ”ฏๆŒ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ใ€้›†ๆˆ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ทจ่ฟ›็จ‹๏ผ‰ +> 1. CQRS +> 2. ้ข†ๅŸŸๆœๅŠก +> 3. ๆ”ฏๆŒ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ใ€้›†ๆˆ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ทจ่ฟ›็จ‹๏ผ‰ > 4. ๆ”ฏๆŒๅฏน้ข†ๅŸŸไบ‹ไปถๅ…ˆๅŽ‹ๆ ˆๅŽ็ปŸไธ€ๅ‘้€ ### 6. DDD @@ -99,36 +105,40 @@ MASA.Contrib ### 7. Contracts.EF -ๅŸบไบŽEFๅฎž็Žฐ็š„่ง„็บฆ๏ผŒ[็”จๆณ•ไป‹็ป](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/Data/MASA.Contribs.Data.Contracts.EF/README.zh-cn.md) +ๅŸบไบŽEFๅฎž็Žฐ็š„่ง„็บฆ๏ผŒ[็”จๆณ•ไป‹็ป](src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md) > ไผ˜ๅŠฟ๏ผš > > 1. ๆŸฅ่ฏข็š„ๆ—ถๅ€™่ฟ‡ๆปคๅทฒๅˆ ้™ค็š„ไฟกๆฏ -> 2. ๆŸฅ่ฏขๅŽๅผ€ๅฏไบ‹ๅŠก -> 3. ่ฝฏๅˆ ้™ค +> 2. ่ฝฏๅˆ ้™ค ```C# -Install-Package MASA.Contribs.Data.Contracts.EF +Install-Package MASA.Contrib.Data.Contracts.EF ``` ```C# -builder.Services - .AddUoW(dbOptions => +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => { dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); dbOptions.UseSoftDelete(builder.Services);//ๅฏๅŠจ่ฝฏๅˆ ้™ค - }) + }); +}); ``` > ๅฝ“ๅฎžไฝ“็ปงๆ‰ฟISoftware๏ผŒไธ”่ขซๅˆ ้™คๆ—ถ๏ผŒๅฐ†ๅˆ ้™ค็Šถๆ€ๆ”นไธบไฟฎๆ”น็Šถๆ€๏ผŒๅนถ้…ๅˆ่‡ชๅฎšไน‰Removeๆ“ไฝœ๏ผŒๅฎž็Žฐ่ฝฏๅˆ ้™ค > ๆ”ฏๆŒๆŸฅ่ฏข็š„ๆ—ถๅ€™ไธๆŸฅ่ฏข่ขซๆ ‡่ฎฐ่ฝฏๅˆ ้™ค็š„ๆ•ฐๆฎ > ไธŽEventBus็ป“ๅˆไฝฟ็”จๆ—ถ๏ผŒๅšๅˆฐไบ†็ฌฌไธ€ๆฌกCUDๅŽๅผ€ๅฏไบ‹ๅŠก๏ผŒๅฝ“ๆ•ดไธชHandlerๅ‡บ็Žฐๅผ‚ๅธธๅŽๆ”ฏๆŒไบ‹ๅŠกๅ›žๆปš +### 8. MASA.Contrib.Configuration + +้‡ๅฎšไน‰Configuration๏ผŒๆ”ฏๆŒLocalใ€ConfigurationAPI่Š‚็‚น็š„็ฎก็†๏ผŒ็ป“ๅˆIOptionsใ€IOptionsMonitorๅฎŒๆˆ้…็ฝฎ็š„่Žทๅ–ไปฅๅŠ้…็ฝฎ็š„ๆ›ดๆ–ฐ่ฎข้˜… [Local็”จๆณ•ไป‹็ป](src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md) ใ€[Dcc็”จๆณ•ไป‹็ป](src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md) + ## ๅ•ๅ…ƒๆต‹่ฏ•่ง„ๅˆ™ ไธบ็กฎไฟๆ•ดไธชๆบ็ ็š„ๅฏ้ ๆ€ง๏ผŒๅ•ๅ…ƒๆต‹่ฏ•่ฆ†็›–็Ž‡ๆœ€ไฝŽไธบ90% ## โ˜€๏ธ ๆŽˆๆƒๅ่ฎฎ -[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/LICENSE) +[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) diff --git a/docs/LoadEvent.md b/docs/LoadEvent.md new file mode 100644 index 000000000..65d1db9e3 --- /dev/null +++ b/docs/LoadEvent.md @@ -0,0 +1,10 @@ +# LoadEvent + +## Getting "Event" relationship chain failed + +When Event, EventHandler, and the main project are not in the same assembly, there will be a failure to obtain the "Event" relationship chain when publishing Events through EventBus. When we use AddEventBus without a special specified assembly, the assembly under the current domain is used by default. Due to the delayed loading feature of dotnet, the acquisition of the event relationship chain is incomplete. There are the following two solutions : + +1. When using AddEventBus, specify the complete set of application assemblies used by the current project by specifying Assemblies + +2. Before using AddEventBus, by calling Event directly +Any method or class of the assembly where the EventHandler is located, make sure that the application assembly where it is located has been loaded into the current assembly ( AppDomain.CurrentDomain.GetAssemblies() ) diff --git a/nuget.config b/nuget.config deleted file mode 100644 index b3dcc28ac..000000000 --- a/nuget.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/packageIcon.png b/packageIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..2128805c1995656deba83316df54cb048b9264ee GIT binary patch literal 9523 zcmb_?RZtvV)GbMH4=#bh6C4s;1_`dg-8DgjI|&-xAvl9OgA6V~Cpf_dcXt_dkjq#1 z`99qG|A+3bK7046>Z-NRS!`}+MOv^g^lRHswFY3f}@9~klao-i!G2gNH zySuv|;>rBYZCF1_vuMm{8D3#v(PGHyqRWf-NXoq?Adw`92jeIH2nmfxW?&0p_#dT+ zx3(b>BY*zv^D#9$mfVeOJXjK=^U3gPq1}+S$|x44N}p@!BN?7}B8`SKi&IYN5ngBF zs);oV2_(1OIm^#Edrfz-YXcdX-|Vw*Z4GXT-R$P@ziO3WMj(?$PW=2=WRtBPGE|_} ze4Z&^GxPDbL5_#XJFr3vL59^DT;k%E`r;a;t~BRpvXFi85a&LHHhQuG?jeYqXg(N% zX1Vj}zu1OO4347=%wZ&6bN{&Who+=izhS@5s5S#Mg~<;aeG6!4x_4XdYEG1t5@`~n zZtJ3Gp#$1jaQ}pik~VM>C0|64s*TfClT0Di?6!8Bb@B?>GObvJ9`O`xwKi>R6`*pr$jH3Mt+~0m+Z`~xxr$xz zs`F8tHnbc`yd-Qp_XD<&aG*JP^I4oErdGPla&?v_IGPAnO(SBg`!=(b(uUz(OjO6` z2LV}Q_)XmDm;FJ4h0#%lYT3owLuRi=@MKc$vY6r%T=lr5S|&myNjpZgowjAU52|^f zAunQ%vNqqNe^X>=KufzZ2FYI^rYzaEdiT#c5e+8lJ~tg4dbLLqu&s%rEWg~Qv?8Lj zCsH?c-q43xmzkna{NA^4>cZ4a`lMnaVoWUXgBx{39Lw~{V+HkogNUx~Zk)C)%4&-g z&^PGSpZZ%&NM^cZ`6W2AL6^g;9K3O|PCiI%;6?3dMC0=5dvZ&iMVl1sm%qj>n~|NDVW5TB@rL>gdHdndI!Q(w~Xq z(7D_3{PKC062HjdgZh9OiVRI38Gmc#$fNI^a4C9it3tS6C`rnI^ZOO2KcCq8xWM6x-y1QQF1 zqQTh{gC(Rb4HP|0yd3l%t3vRvhye2vGqz8&d-rna2V!bAHU(5r$C43xL`2I@PVH0O zW_Qo)A50E3;Mg`ST5=)wrE4Y6?emv0@QS$4fs)U6F6^ua#a~OJL~*34fjE2IbIYOe zKMrkZX5I(~Tfe{r=}Qf>+tL1sWHRebwU0BsZGK_{or5uIP)HQCd+>P7*z&|MDC+%^ z(XWph@*yZl>?W^MNV6Ud_VRr{$ho$E^y~$Qb5)t?8|~|lc-C`^4k<;-QumuF!qq#1_qE`M4Yz- zYqS-uAhmh5#D;|@sq||W5O`#|l>Nc&7;S$rJ_YrzC2tDJf=%N`E}20w-9MQLJBe&o zuQIqLmyf-GE?tVAE|VPQw^_n~p-|Yy8rc?XVec2RN6SnJr+4q8mq6|uw zxy3Z6=}8<9yP2EzDwUV%X1=w8f+~=~RVE(6%c$%k;}+frK{E#s@^>|RkjqyR_SsDD z7~d@Df;ue$df1rGsWE%Ydff3+PzpW>e4cl10EN%(nv$((67o zuyhhC#-_u=@wnf*waXzFz4f6eNDgg#7|d;r00D#Ae{gTEzXwbQqJv*Pu10Y#s4C*S}TYq^1lFsx_<0XZ8wd{Ifb6xo_r>iYGhkI5f^~Zh>qmgxQD>0PRly zY}r>c9ZXrBZPLWe@0E29c^myc7A%}kfH|zB%|h*&rVv>WiT8u+T{+B}q9HFhXxPS< zWrreQkN;rAERx;hB%Fq}kafz5XL+A@Jkx?BVTg^55F2NH-^-W;G}wzR6m7+oLz@4g zB}nJqxl^m9oL83__^J}t)0^pKh@EgxG(7kRt{-g?`k`C_$)tdfbSc?xA@=H=HL#5n zJ;g*9y=m=k8Ltluk0hRW zI=kiquV(CAa16dH)YKWON_J+l#kJYB+~m?jw%x5@Ay?3E^Ygu-CDV2T94lq4+)*Fj zzd;g{5(JJtfO*-+N#%W6XgZrtbp(Hb|)nix1{$eVDRgST0({ z;&)HVj?G-XyC{O!pR1k_BKXyuR5)k>$KwD?Yb|2D&%L0kd@FvN=VGSOPek<@;x(aX z5s{0eN2}5buRDFSk0!L|A4V6L9H@jqQ)tqzT^S-r7xHybQ`oGRk)SkNHfZ|m(L)4Z z&(+k{)s*GUPk4hg>Lpz5$Vh~xNBv@lY7V@@nRTI{oIc&UYvb)=_+4JEiDOOHQ$W}% zkTH>~sh$a2U*MswcM9_DHzIa2p?qX-`fQXzT0im8g#yRQ7x9aaJ%)DH~0(n3cTbll}O++Dl7% zKEaW<0_`zAUQ=brDOuPLyEVt-pK=#u7fQ*+ow)?}IV_JIj2S#X2E+Go=aUf6gjB_K zos4m(_-FwIVbS&UxmCkk(uX5&Genx*mSSCp6nblNX^nmQW4*>BLqF*B{ACU+KGw_W zH9uW{AvDo8sVsh8NfUP6#ai0}0^s>rwbY;fU0*E3Tv^0!XHi6$RSHwIK}yd}=?9SJ zO3kAOwU6J#tnM6vcsd0UKBFx1d?wt)Dys>3EJ~NKM1# zo7h(!T?{;_lY`*9d!2A%p(Z{vdsx6ov;>QCPTtY0Q?gaWh#c{`UYygWD)rorw`}9E zjEd{GTH6ugf5fj3T`rsUB6hnDm(m>O5*Ojw?WU@49bKBcPdYS#LlV08L8#wXn1=5= zAKtC=kJWIRk_&+Q^CeGNL`?I>nBg9zacT$ef|~@~Bls2^?+6%H2&(jz0~RFx-H`8g zh*~?};F(b=t_vW~Y$oO+sIz5$W%*!p=pkqLCFoqAZ(`JHqje~nL@)@o_u!I!9^T3u z`zd-DD#vI`x%@|4QnfZrtk?E16#gW`=>^43MRbH$)3(EvO38vvnc;(;X;S?E5*o z^@BV0!8$19&VWtv0`kp%eB9$Y>sOniE*>*4j#+Rf`m7#__E@4U0Ket=$W0<`_nG#l za(BmRR&N#ZRnFOHurzJlGac?IIv5Jtc?|gblT;_;& zNnws}Fz%~+^wm>A)y5xPLu+bo)RRq~bA^*jdmeK}&f(dEqNP0)ZAs{ab_V^;7m#YU zKW&#>C*27$Qg%2egQ?NHTllAn0EadZwYAZD%Yphti@rvqzKu<@&&}ooG&85HWxws3 z3vyzTY-i49bc36}%RIQ=KUxBfqF9nz(> zTp0c83<0{(P>8z~fp#1%31?!`&zcN&n4Q@=QK&mT@oY)RE84KIOIdcj>aXYq71V&q zlqv$#vdtr&hXK7s)6!=$gQiN2&zi`P4Z{95(2B{k!S|;RYDyhX5Q)MkFc4c_!j^Lw zqv0)G0K1@){aoVVMDI0qF0jY5GGRPGk+(Vuw$oqN(5GIFz1S7e>JJLZaq`1HhxrV5 zJis9j1M>tF`Mx^8SQBtLDFg8k>#jwDiQUe>J7i$h6?Qdp=^?yZq89b|Q5_W}e`^L< zstmRr-|XvzB~P&AE-8MyV0*?n{G6$MeMQMGRJ%8)uIuD6qJ;33Jr^;IVj@-14!Gr< z`N^*N59jtpC*V~ZgWd?_>BfBr66S<^;tKg>E1wYlQ;#D_vT%+yjN)_t(ot z9_?0#%5TixwHUm8YpOYjEAim>>(%&aDZk#=-bYk^_jLcJC(?WKDFJXtGf{|lIu`oI z$Z421PFie|Xn4w+TLo+*?c|?>0qtU!{Jbw~@GNGs`9N1}-D63Nsjqm)7H=+h^(x=CShwVK0wuOB)Oc|Lr~9%Q$4_^QzZu2h&JAew#WY3_KLw5;obKJJR|I^#m8BEC4bR8oPX=wbKfwKT-{F&gWzkTaLz2@zOM zSMGuijR=uWK7PZ__9m*B1#&Q%WdOX?)~X2BlNWde7=sDdD?BcVNqJ=0p@6A>bM2WO zew>#L?g+S>^>F(P3QEwhPjOgi*wlNiZC)}MM!#s)ugV2o=@+BOzu$1s>iN8AT`g}= z-6o($Ck74B_`7g2-Ok)>7GNzIo za?8^2oamL`n?*Zcqt73@3v=7+6I}1&)oCD@HAa9=SuWRc_r=Fj94w!tH3?joEq?DY zGXfjg&k7dv_5SWq{pRF97>zKmupU-DYIHAxP`b<#UZORrj|%Jhuz$aCK4tOajz}%$ z(2?WcIXzA-kGR#Eb)Ye-DYL^dZ^6KVb{D;Bm}_fxuPdz)dtN|CeBYR-Pn{O@m!KP^ z1WdHSh|7oi#vpZj!CQYk_Xn!LU)g-i1YfT6k=neYCsnU`?ej zz?~!2?s@ZY%b|E|U9_ItQFH)|Kbg$MK;N2anX5J0O8>m^^n6+K$_o)e4eJ=TeDDX^ zKwX{p8Qv~LDU+7m^L!gk*GkF6NK;)XYx6Lg``ba{`tPk~J-)|8o6^tI?wOP%$ z0=coNoB~T2S_nFC<#kHl1|EuhJ<;5UfSv1HW@y>BBLU6^xbf~?*hu$X4)1uEJHdVx z7qhNdXn};1w-J}v>x9X(CdG2{fB3_Mk3z5C$sq2rTxxS#9SH~8Z%h!irs`~b4oF|A zO*-6Tm6ZD;I|Yy1>PHvqZ))_a2VL=rC8VY2@PmJfgVh8%a~bOF`~mNB7F_ukk2C+=B>7|g2Q zld49ewxJ$sxs?~0o`{t>Y%NDQ}5>j&Ry{_n;IT;0TB9L?$#x^ zT0g!%yXI{YwmSv!P`?=dLRbW*IOY1FKi&H`oVGto2dV@Q<6^#EAS<(FNr?geI@ zIy+h0EgNWb35p@mjJUr(tprH=>i89+AQ5(b{4MjW%*FHaA0N0CXQS3YmI$Q#XfNX6|8O-`$Y-*ri1*0<*lTu72_QQLLiKT)%T^E4 zj%H^Z*dDGXF?x~-&F=S?(s0q)#%$A`DkZH1b9UDwJHW+%=r3Gn4||)wF1njVd4w~k zI|mOxO-5%aEW)4dc-Ok-i|eV;_#}pqiD&Hw!rd&=j%RpIO)EfP;7GC)e0ID~$okuA zu#uEoAqvn&1AYI{tSOyp&!QwWQK{pb-=r+phTwRA1Uhn!|sA>sw9sB zvSUA~uTB6ii92hzSK3Fp%BX`-3c1_)u=lNWe^Pf&4P|-qd)(n*D`BNik<$G|8uJ{{ z7a-Li{GZS7iFvqXd~uXeH)<^iEh3cat4UT%jFa;s6G0m*gr!o!LLla;57{c^Bbtbg zZspZMnZ~QJVVCP~z5$6;_f>SA0$%p-A?K8W1SCjo>9l~=<;9}6Ws~a}U|ags++faZ zTF%yA@Et+(=bDefxL(E=lJp8Vy{3PHB{;pQQmhqrSBPJ{`nquglJB)__UN7-yj(po zBgO@=vTnuunwbE@0L&^a`*`i9;42xpMB|krf z|Gg!X-0$!DIPK92d?fd{M@qjZ(($VH^w%OOdR9n$wS8}@J^WR<)(1OLKw3hOG=N5fAY>!7bAZRrr02L0*slU$ znj$!7c`{7vw0|VY-<(PWPoWLI6y9+7DLkX}Px189a6+LFX@y$mA@^_YxO&+W#`>vR z(1zb28vhxVth_Q%yNc;%2!Fum%hQ7zoiLTJgMh0oYf8qia5t--dx4balLdjp2c zW9GMg-FN)9zdmYdJ=~o+2x;VOWpS66&$MhK8j1xUgh|g)2sM4!@e7?f!g?!HTB{$a zR%WWOZi8;LcC=m`71w}C_Yb-r56j_^SkS*a$Me5;>-!Zrb!$xs;UYNqgZ@XC|5%i% znUaG4(adGk%E4T9K~=*EQn$$4ilXVP38^>EM>b*Wr=(-!&TMR%Wnkl?xqGXfYZR9U z(y;Dven7K#>TfZLL9(C8KbfJ{3z8kn(HMj}bP-O#FD`8HM8DPRtT|xmFu|3UkU(CQ zeljc)or70i|7PfJ5?F1Rkyr&_=$OQ8%LUw{YGV{2Kewpc&0_s?zpTgcnd$2pmo=&)5ZRkCw&2m%sqxcH?kEZW63jcPpMo%b)e^c zxnH@<3l;KQ)~XRI#Hj48%BdPrX)h{Npm@gQJ#YZK&nQO@J_Q{1o_vvn0R*!f zzzNKq-RjtLtPjhoTA)h)1A7!TIzczcvte;`P<76SluVIdV!>@{S`w0hj z`MwscZ7B`2E_m*Cij_KSuUS<6L!b!8-u3Brkf`G0&t^KnQX)DOMSST9HWoj{TxGgq zrW{wZ^nV%hV>1c-eP87`=(e4NklO3otA2CT^rZxkAiQyifO?IMwPE$$dpK(q-k06( zG>eqMb1%e6v|z3jIt^xB`?D_N;-kiTld?n;Bu@>)P(0{!+hR?KkD-BaXInecce z;sk}O^}j=l?Mu$-+84f?wF-5dDJ2a^LWrQTZjv|pryR|C)A1ADTx=_aa@)jp2+lWq zRJ)8VmJ7u+$5+3waopZ18`V&(Fx4-=>!~1jJG=HR+4rL?&PHair=vlg^a{1Y0>c*9 zXM9C5bw1l}l?E_p6nUXONyO*`4do66fI%ftbf+;ftkFj z)?8C{HRi(qVkgf8Njk}@{8DMWlC`sSJ8*FyWvq%8?p+S0G%^*<$eg3`yE5a}`%fr2 zKg@y82T(Vbgk&?bGpfa0%0b$1{)JMA!JzqNk>UIiZwX_LTkA3m=bkG@gyi;s@yAQD z{wGd!EI~qNnQZ3u_A5+Aa{R&@T-zn>Vz4|Srx?AGgi=#Xm{^5zG4`Zt>fx2p$Wp+( z^EQ#0!XdZ2uD*wPpph->%cqP5~9!!Ol@(aCj4jV}ZqZ55IB^F)rWnZc+*=o)ZD*P4Os} zZgsYGmBDohUhR(zzZ(hr8;PUz-oC{Bp--~tZ9SNdBg<{rn4Fy!s%g0f=gD6cWcP7m zHBpetgQ|ioPB)5qY9X%2(f<5cW{4Q;a_wCk4%n{s`f5NP*VT_!xJA3)-DBpKO&mVk zn>#_t@slb36jZd;ic?^`TIpKN)y1VOMliKw4t+6PD}6CxgB$070XU!x`tFAv@U6?v zr0|GauJkFlGqFUZ-kvxYMyC?X~ zqNjXq?yjL&-G@w8mNj)lzE|3)a=CNt*rIJDm*@uY&E}!lQ2SZvfdbQrqF~rNJ@<9v z8{9c)8bF)wy|*gy?Sj_)do=5`D)RlvG2@IkSrZYD@UPTj&5^r#%! zUx9pGQO%=20Lp-GvOs} zo-g1CT#mg>FVod7KX!f5f*d(C@&uB;+)bPWFB|}PtH0{hP?}gR^Y)y(d5BWz%3Q&a z0b1wG9{H;)4xzpM(qh8bcJLl*p`(%@g2(wB1+t7!58{0huqheokw!p?t!?y~f1q%y z63B~hBJp0R&N+9C(9)Xag_!{sqH0%CR^WRF{F@3(?A@rzdpfveaC}Jakzg{n*dwpW z-?)>!&iNg1uE~Cxrh#}{)UVO$*`kZ#gpo`D#kRuBj`iYp&wTqcuT=k_`cp((Fb~!f z=Ip&?Jk>> _taskExpandoObjects = new(); + private readonly ConcurrentDictionary>> _taskJsonObjects = new(); + + public ConfigurationApiClient( + IServiceProvider serviceProvider, + IMemoryCacheClient client, + JsonSerializerOptions jsonSerializerOptions, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + : base(defaultSectionOption, expandSectionOptions) + { + _serviceProvider = serviceProvider; + _client = client; + _jsonSerializerOptions = jsonSerializerOptions; + } + + public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string environment, string cluster, string appId, + string configObject, Action valueChanged) + { + var key = FomartKey(environment, cluster, appId, configObject); + return GetRawByKeyAsync(key, valueChanged); + } + + public async Task GetAsync(string environment, string cluster, string appId, string configObject, Action valueChanged) + { + var key = FomartKey(environment, cluster, appId, configObject); + + var value = await _taskJsonObjects.GetOrAdd(key, (k) => new Lazy>(async () => + { + var options = new JsonSerializerOptions(_jsonSerializerOptions); + options.EnableDynamicTypes(); + + var result = await GetRawByKeyAsync(k, (value) => + { + var result = JsonSerializer.Deserialize(value, options); + + var newValue = new Lazy>(() => Task.FromResult((object)result!)); + _taskJsonObjects.AddOrUpdate(k, newValue, (_, _) => newValue); + valueChanged?.Invoke(result!); + }); + if (typeof(T).GetInterfaces().Any(type => type == typeof(IConvertible))) + { + if (result.ConfigurationType == ConfigurationTypes.Text) + return Convert.ChangeType(result.Raw, typeof(T)); + + throw new FormatException(result.Raw); + } + + return JsonSerializer.Deserialize(result.Raw, options) ?? throw new ArgumentException(nameof(configObject)); + })).Value; + + return (T)value; + } + + public async Task GetDynamicAsync(string environment, string cluster, string appId, string configObject, + Action valueChanged) + { + var key = FomartKey(environment, cluster, appId, configObject); + + var value = _taskExpandoObjects.GetOrAdd(key, (k) => new Lazy>(async () => + { + var options = new JsonSerializerOptions(_jsonSerializerOptions); + options.EnableDynamicTypes(); + + var raw = await GetRawByKeyAsync(k, (value) => + { + var result = JsonSerializer.Deserialize(value, options); + var newValue = new Lazy>(() => Task.FromResult(result)!); + _taskExpandoObjects.AddOrUpdate(k, newValue!, (_, _) => newValue!); + valueChanged?.Invoke(result!); + }); + + return JsonSerializer.Deserialize(raw.Raw, options) ?? throw new ArgumentException(nameof(configObject)); + })).Value; + + return await value; + } + + public async Task GetDynamicAsync(string key) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentNullException(nameof(key)); + + var configuration = _serviceProvider.GetRequiredService(); + key = key.Replace(".", ConfigurationPath.KeyDelimiter); + return await Task.FromResult(Format(configuration.GetSection(key))); + } + + private async Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawByKeyAsync(string key, Action valueChanged) + { + var raw = await _client.GetAsync(key, (value) => + { + var result = FormatRaw(value); + valueChanged?.Invoke(result.Raw); + }); + + return FormatRaw(raw); + } + + private (string Raw, ConfigurationTypes ConfigurationType) FormatRaw(string? raw) + { + if (raw == null) + throw new ArgumentException("configObject invalid"); + + var result = JsonSerializer.Deserialize(raw, _jsonSerializerOptions); + if (result == null || result.ConfigFormat == 0) + throw new ArgumentException("configObject invalid"); + + switch (result.ConfigFormat) + { + case ConfigFormats.Json: + return (result.Content!, ConfigurationTypes.Json); + + case ConfigFormats.Text: + return (result.Content!, ConfigurationTypes.Text); + + case ConfigFormats.Properties: + var properties = PropertyConfigurationParser.Parse(result.Content!, _jsonSerializerOptions); + if (properties == null) + throw new ArgumentException("configObject invalid"); + + return (JsonSerializer.Serialize(properties, _jsonSerializerOptions), ConfigurationTypes.Properties); + + default: + throw new NotSupportedException("Unsupported configuration type"); + } + } + + private string FomartKey(string environment, string cluster, string appId, string configObject) + => $"{GetEnvironment(environment)}-{GetCluster(cluster)}-{GetAppId(appId)}-{GetConfigObject(configObject)}".ToLower(); + + private dynamic Format(IConfigurationSection section) + { + var childrenSections = section.GetChildren(); + if (!section.Exists() || !childrenSections.Any()) + { + return section.Value; + } + else + { + var result = new ExpandoObject(); + var parent = result as IDictionary; + foreach (var child in childrenSections) + { + parent[child.Key] = Format(child); + } + return result; + } + } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs new file mode 100644 index 000000000..f8f5036a9 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs @@ -0,0 +1,28 @@ +namespace MASA.Contrib.BasicAbility.Dcc; + +public class ConfigurationApiManage : ConfigurationAPIBase, IConfigurationApiManage +{ + private readonly ICallerProvider _callerProvider; + + public ConfigurationApiManage( + ICallerProvider callerProvider, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + : base(defaultSectionOption, expandSectionOptions) + { + _callerProvider = callerProvider; + } + + public async Task UpdateAsync(string environment, string cluster, string appId, string configObject, object value) + { + var requestUri = $"open-api/releasing/{GetEnvironment(environment)}/{GetCluster(cluster)}/{GetAppId(appId)}/{GetConfigObject(configObject)}?secret={GetSecret(appId)}"; + var result = await _callerProvider.PutAsync(requestUri, value, default); + + // 299 is the status code when throwing a UserFriendlyException in masa.framework + if ((int)result.StatusCode == 299 || !result.IsSuccessStatusCode) + { + var error = await result.Content.ReadAsStringAsync(); + throw new HttpRequestException(error); + } + } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs new file mode 100644 index 000000000..fbd4b6c6e --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal; + +internal enum ConfigFormats +{ + Properties = 1, + Text, + Json +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs new file mode 100644 index 000000000..cbbd6b8c5 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs @@ -0,0 +1,37 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal; + +public class ConfigurationAPIBase +{ + private readonly DccSectionOptions _defaultSectionOption; + private readonly List _expandSectionOptions; + + protected ConfigurationAPIBase(DccSectionOptions defaultSectionOption, List? expandSectionOptions) + { + _defaultSectionOption = defaultSectionOption; + _expandSectionOptions = expandSectionOptions ?? new(); + } + + protected string GetSecret(string appId) + { + if (_defaultSectionOption.AppId == GetAppId(appId)) + return _defaultSectionOption.Secret ?? ""; + + var option = _expandSectionOptions.FirstOrDefault(x => x.AppId == appId); + if (option == null) + throw new ArgumentNullException(nameof(appId)); + + return option.Secret ?? ""; + } + + protected string GetEnvironment(string environment) + => !string.IsNullOrEmpty(environment) ? environment : _defaultSectionOption.Environment!; + + protected string GetCluster(string cluster) + => !string.IsNullOrEmpty(cluster) ? cluster : _defaultSectionOption.Cluster!; + + protected string GetAppId(string appId) + => !string.IsNullOrEmpty(appId) ? appId : _defaultSectionOption.AppId!; + + protected string GetConfigObject(string configObject) + => !string.IsNullOrEmpty(configObject) ? configObject : throw new ArgumentNullException(nameof(configObject)); +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs new file mode 100644 index 000000000..d352c723b --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs @@ -0,0 +1,12 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal; + +internal class Constants +{ + internal const string DEFAULT_CLIENT_NAME = "masa.plugins.caching.dcc"; + + internal const string DEFAULT_SUBSCRIBE_KEY_PREFIX = "masa.dcc:"; + + internal const string DEFAULT_ENVIRONMENT_NAME = "ASPNETCORE_ENVIRONMENT"; + + internal const string DATA_DICTIONARY_SECTION_NAME = "DataDictionary"; +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs new file mode 100644 index 000000000..40158fc81 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs @@ -0,0 +1,92 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal; + +internal class DccConfigurationRepository : AbstractConfigurationRepository +{ + private readonly IConfigurationApiClient _client; + + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; + + private readonly ConcurrentDictionary> _dictionaries = new(); + + private readonly ConcurrentDictionary _configObjectConfigurationTypeRelations = new(); + + public DccConfigurationRepository( + IEnumerable sectionOptions, + IConfigurationApiClient client, + ILoggerFactory loggerFactory) + : base(loggerFactory) + { + _client = client; + foreach (var sectionOption in sectionOptions) + { + Initialize(sectionOption).ConfigureAwait(false).GetAwaiter().GetResult(); + } + } + + private async Task Initialize(DccSectionOptions sectionOption) + { + foreach (var configObject in sectionOption.ConfigObjects) + { + string key = $"{sectionOption.Environment!}-{sectionOption.Cluster!}-{sectionOption.AppId}-{configObject}".ToLower(); + var result = await _client.GetRawAsync(sectionOption.Environment!, sectionOption.Cluster!, sectionOption.AppId, configObject, (val) => + { + if (_configObjectConfigurationTypeRelations.TryGetValue(key, out var configurationType)) + { + _dictionaries[key] = FormatRaw(sectionOption.AppId, configObject, val, configurationType); + FireRepositoryChange(SectionType, Load()); + } + }); + + _configObjectConfigurationTypeRelations.TryAdd(key, result.ConfigurationType); + _dictionaries[key] = FormatRaw(sectionOption.AppId, configObject, result.Raw, result.ConfigurationType); + } + } + + private IDictionary FormatRaw(string appId, string configObject, string? raw, ConfigurationTypes configurationType) + { + if (raw == null) + return new Dictionary(); + + switch (configurationType) + { + case ConfigurationTypes.Json: + return SecondaryFormat(appId, configObject, JsonConfigurationParser.Parse(raw)); + case ConfigurationTypes.Properties: + return SecondaryFormat(appId, configObject, JsonSerializer.Deserialize>(raw)!); + case ConfigurationTypes.Text: + return new Dictionary() + { + { $"{appId}{ConfigurationPath.KeyDelimiter}{DATA_DICTIONARY_SECTION_NAME}{ConfigurationPath.KeyDelimiter}{configObject}" , raw ?? "" } + }; + default: + throw new NotSupportedException(nameof(configurationType)); + } + } + + private IDictionary SecondaryFormat( + string appId, + string configObject, + IDictionary data) + { + var dictionary = new Dictionary(); + foreach (var item in data) + { + dictionary[$"{appId}{ConfigurationPath.KeyDelimiter}{configObject}{ConfigurationPath.KeyDelimiter}{item.Key}"] = item.Value; + } + return dictionary; + } + + public override Properties Load() + { + Dictionary properties = new(); + foreach (var item in _dictionaries) + { + foreach (var key in item.Value.Keys) + { + properties[key] = item.Value[key] ?? string.Empty; + } + } + return new Properties(properties); + } + +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs new file mode 100644 index 000000000..039bee5b4 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs @@ -0,0 +1,20 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal; + +internal class DccFactory +{ + public static IConfigurationApiClient CreateClient( + IServiceProvider serviceProvider, + IMemoryCacheClient client, + JsonSerializerOptions jsonSerializerOptions, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + { + return new ConfigurationApiClient(serviceProvider, client, jsonSerializerOptions, defaultSectionOption, expandSectionOptions); + } + + public static IConfigurationApiManage CreateManage( + ICallerFactory callerFactory, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + => new ConfigurationApiManage(callerFactory.CreateClient(), defaultSectionOption, expandSectionOptions); +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs new file mode 100644 index 000000000..760b9cc23 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace MASA.Contrib.BasicAbility.Dcc.Internal.Model; + +internal class Property +{ + public string Key { get; set; } = default!; + + public string Value { get; set; } = default!; +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs new file mode 100644 index 000000000..b94b53372 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace MASA.Contrib.BasicAbility.Dcc.Internal.Model; + +internal class PublishRelease +{ + public ConfigFormats ConfigFormat { get; set; } + + public string? Content { get; set; } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs new file mode 100644 index 000000000..b23f71761 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs @@ -0,0 +1,101 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal.Parser; + +/// +/// https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs +/// +internal sealed class JsonConfigurationParser +{ + private JsonConfigurationParser() + { + } + + private readonly Dictionary _data = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Stack _paths = new Stack(); + + public static IDictionary Parse(string json) + => new JsonConfigurationParser().ParseJson(json); + + private IDictionary ParseJson(string json) + { + var jsonDocumentOptions = new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + var doc = JsonDocument.Parse(json, jsonDocumentOptions); + + if (doc.RootElement.ValueKind != JsonValueKind.Object) + { + throw new FormatException($"[{doc.RootElement.ValueKind}]Invalid top level JsonElement."); + } + + VisitElement(doc.RootElement); + + return _data; + } + + private void VisitElement(JsonElement element) + { + var isEmpty = true; + + foreach (JsonProperty property in element.EnumerateObject()) + { + isEmpty = false; + EnterContext(property.Name); + VisitValue(property.Value); + ExitContext(); + } + + if (isEmpty && _paths.Count > 0) + { + _data[_paths.Peek()] = ""; + } + } + + private void VisitValue(JsonElement value) + { + Debug.Assert(_paths.Count > 0); + + switch (value.ValueKind) + { + case JsonValueKind.Object: + VisitElement(value); + break; + + case JsonValueKind.Array: + int index = 0; + foreach (JsonElement arrayElement in value.EnumerateArray()) + { + EnterContext(index.ToString()); + VisitValue(arrayElement); + ExitContext(); + index++; + } + + break; + + case JsonValueKind.Number: + case JsonValueKind.String: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + string key = _paths.Peek(); + if (_data.ContainsKey(key)) + { + throw new FormatException($"[{key}]key is duplicated."); + } + + _data[key] = value.ToString(); + break; + + default: + throw new FormatException($"[{value.ValueKind}]Unsupported json token."); + } + } + + private void EnterContext(string context) => + _paths.Push(_paths.Count > 0 ? _paths.Peek() + ConfigurationPath.KeyDelimiter + context : context); + + private void ExitContext() => _paths.Pop(); +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs new file mode 100644 index 000000000..fb05155cd --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs @@ -0,0 +1,7 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Internal.Parser; + +internal class PropertyConfigurationParser +{ + public static IDictionary? Parse(string raw, JsonSerializerOptions serializerOption) + => JsonSerializer.Deserialize>(raw, serializerOption)?.ToDictionary(k => k.Key, v => v.Value); +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj new file mode 100644 index 000000000..7830d62c8 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj @@ -0,0 +1,23 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs new file mode 100644 index 000000000..ac6c63bb9 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs @@ -0,0 +1,204 @@ +namespace MASA.Contrib.BasicAbility.Dcc; + +public static class MasaConfigurationExtensions +{ + public static IMasaConfigurationBuilder UseDcc( + this IMasaConfigurationBuilder builder, + IServiceCollection services, + Action? jsonSerializerOptions = null, + Action? callerOptions = null) + => builder.UseDcc(services, "Appsettings", jsonSerializerOptions, callerOptions); + + public static IMasaConfigurationBuilder UseDcc( + this IMasaConfigurationBuilder builder, + IServiceCollection services, + string defaultSectionName, + Action? jsonSerializerOptions = null, + Action? callerOptions = null) + { + if (!builder.GetSectionRelations().TryGetValue(defaultSectionName, out IConfiguration? configuration)) + throw new ArgumentNullException("Failed to obtain Dcc configuration, check whether the current section is configured with Dcc"); + + var configurationSection = configuration.GetSection("DccOptions"); + var dccOptions = configurationSection.Get(); + + List expandSections = new(); + var configurationExpandSection = configuration.GetSection("ExpandSections"); + if (configurationExpandSection.Exists()) + { + configurationExpandSection.Bind(expandSections); + } + + return builder.UseDcc(services, () => dccOptions, option => + { + option.Environment = configuration["Environment"]; + option.Cluster = configuration["Cluster"]; + option.AppId = configuration["AppId"]; + option.ConfigObjects = configuration.GetSection("ConfigObjects").Get>(); + option.Secret = configuration["Sectet"]; + }, option => option.ExpandSections = expandSections, jsonSerializerOptions, callerOptions); + } + + public static IMasaConfigurationBuilder UseDcc( + this IMasaConfigurationBuilder builder, + IServiceCollection services, + Func configureOptions, + Action defaultSectionOptions, + Action? expansionSectionOptions = null, + Action? jsonSerializerOptions = null, + Action? callerOptions = null) + { + if (services.Any(service => service.ImplementationType == typeof(DccConfigurationProvider))) + return builder; + + services.AddSingleton(); + + var config = GetDccConfigurationOption(configureOptions, defaultSectionOptions, expansionSectionOptions); + + var jsonSerializerOption = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + jsonSerializerOptions?.Invoke(jsonSerializerOption); + services.AddCaller(options => + { + if (callerOptions == null) + { + options.UseHttpClient(() + => new MasaHttpClientBuilder(DEFAULT_CLIENT_NAME, string.Empty, opt => opt.BaseAddress = new Uri(config.DccConfigurationOption.ManageServiceAddress)) + ); + } + else + { + callerOptions.Invoke(options); + } + }); + + services.AddMasaRedisCache(DEFAULT_CLIENT_NAME, config.DccConfigurationOption.RedisOptions).AddSharedMasaMemoryCache(config.DccConfigurationOption.SubscribeKeyPrefix ?? DEFAULT_SUBSCRIBE_KEY_PREFIX); + + TryAddConfigurationApiClient(services, config.DefaultSectionOption, config.ExpansionSectionOptions, jsonSerializerOption); + TryAddConfigurationApiManage(services, config.DefaultSectionOption, config.ExpansionSectionOptions); + + var sectionOptions = new List() + { + config.DefaultSectionOption + }.Concat(config.ExpansionSectionOptions); + + var configurationApiClient = services.BuildServiceProvider().GetRequiredService(); + var loggerFactory = services.BuildServiceProvider().GetRequiredService(); + builder.AddRepository(new DccConfigurationRepository(sectionOptions, configurationApiClient, loggerFactory)); + return builder; + } + + public static IServiceCollection TryAddConfigurationApiClient(IServiceCollection services, + DccSectionOptions defaultSectionOption, + List expansionSectionOptions, + JsonSerializerOptions jsonSerializerOption) + { + services.TryAddSingleton(serviceProvider => + { + var client = serviceProvider.GetRequiredService() + .CreateClient(DEFAULT_CLIENT_NAME); + + if (client == null) + throw new ArgumentNullException(nameof(client)); + + return DccFactory.CreateClient( + serviceProvider, + client, + jsonSerializerOption, + defaultSectionOption, + expansionSectionOptions); + }); + return services; + } + + public static IServiceCollection TryAddConfigurationApiManage(IServiceCollection services, + DccSectionOptions defaultSectionOption, + List expansionSectionOptions) + { + services.TryAddSingleton(serviceProvider => + { + var callerFactory = serviceProvider.GetRequiredService(); + return DccFactory.CreateManage(callerFactory, defaultSectionOption, expansionSectionOptions); + }); + return services; + } + + private static (DccSectionOptions DefaultSectionOption, List ExpansionSectionOptions, DccConfigurationOptions DccConfigurationOption) GetDccConfigurationOption( + Func configureOptions, + Action defaultSectionOptions, + Action? expansionSectionOptions = null) + { + var dccConfigurationOption = configureOptions(); + if (dccConfigurationOption == null) + throw new ArgumentNullException(nameof(configureOptions)); + + if (string.IsNullOrEmpty(dccConfigurationOption.ManageServiceAddress)) + throw new ArgumentNullException(nameof(dccConfigurationOption.ManageServiceAddress)); + + if (dccConfigurationOption.RedisOptions == null) + throw new ArgumentNullException(nameof(dccConfigurationOption.RedisOptions)); + + if (dccConfigurationOption.RedisOptions.Servers == null || dccConfigurationOption.RedisOptions.Servers.Count == 0 || dccConfigurationOption.RedisOptions.Servers.Any(service => string.IsNullOrEmpty(service.Host) || service.Port <= 0)) + throw new ArgumentNullException(nameof(dccConfigurationOption.RedisOptions.Servers)); + + if (defaultSectionOptions == null) + throw new ArgumentNullException(nameof(defaultSectionOptions)); + + var defaultSectionOption = new DccSectionOptions(); + defaultSectionOptions.Invoke(defaultSectionOption); + + if (string.IsNullOrEmpty(defaultSectionOption.AppId)) + throw new ArgumentNullException("AppId cannot be empty"); + + if (defaultSectionOption.ConfigObjects == null || !defaultSectionOption.ConfigObjects.Any()) + throw new ArgumentNullException("ConfigObjects cannot be empty"); + + if (string.IsNullOrEmpty(defaultSectionOption.Cluster)) + defaultSectionOption.Cluster = "Default"; + if (string.IsNullOrEmpty(defaultSectionOption.Environment)) + defaultSectionOption.Environment = GetDefaultEnvironment(); + + var dccCachingOption = new DccExpandSectionOptions(); + expansionSectionOptions?.Invoke(dccCachingOption); + List expansionOptions = new(); + foreach (var expansionOption in dccCachingOption.ExpandSections ?? new()) + { + if (string.IsNullOrEmpty(expansionOption.Environment)) + expansionOption.Environment = defaultSectionOption.Environment; + if (string.IsNullOrEmpty(expansionOption.Cluster)) + expansionOption.Cluster = defaultSectionOption.Cluster; + + if (expansionOption.ConfigObjects == null || !expansionOption.ConfigObjects.Any()) + throw new ArgumentNullException("ConfigObjects in the extension section cannot be empty"); + + if (expansionOption.AppId == defaultSectionOption.AppId || + expansionOptions.Any(section => section.AppId == expansionOption.AppId)) + throw new ArgumentNullException("The current section already exists, no need to mount repeatedly"); + + expansionOptions.Add(expansionOption); + } + return (defaultSectionOption, expansionOptions, dccConfigurationOption); + } + + private static ICachingBuilder AddSharedMasaMemoryCache(this ICachingBuilder builder, string subscribeKeyPrefix) + { + builder.AddMasaMemoryCache(options => + { + options.SubscribeKeyType = SubscribeKeyTypes.SpecificPrefix; + options.SubscribeKeyPrefix = subscribeKeyPrefix; + }); + + return builder; + } + + private static string GetDefaultEnvironment() + => System.Environment.GetEnvironmentVariable(DEFAULT_ENVIRONMENT_NAME) ?? + throw new ArgumentNullException("Error getting environment information, please make sure the value of ASPNETCORE_ENVIRONMENT has been configured"); + + private class DccConfigurationProvider + { + + } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs new file mode 100644 index 000000000..6ee6baf4f --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs @@ -0,0 +1,13 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Options; + +public class DccConfigurationOptions +{ + public RedisConfigurationOptions RedisOptions { get; set; } + + public string ManageServiceAddress { get; set; } = default!; + + /// + /// The prefix of Dcc PubSub, it is not recommended to modify + /// + public string? SubscribeKeyPrefix { get; set; } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs new file mode 100644 index 000000000..e9629637c --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs @@ -0,0 +1,9 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Options; + +public class DccExpandSectionOptions +{ + /// + /// Expansion section information + /// + public List? ExpandSections { get; set; } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs new file mode 100644 index 000000000..fc905907e --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs @@ -0,0 +1,24 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Options; + +public class DccSectionOptions +{ + /// + /// The environment name. + /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty + /// + public string? Environment { get; set; } = null; + + /// + /// The cluster name. + /// + public string? Cluster { get; set; } + + /// + /// The app id. + /// + public string AppId { get; set; } = default!; + + public List ConfigObjects { get; set; } = default!; + + public string? Secret { get; set; } +} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md new file mode 100644 index 000000000..d74017c3a --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md @@ -0,0 +1,164 @@ +[ไธญ](README.zh-CN.md) | EN + +## MASA.Contrib.BasicAbility.Dcc + +Effect: + +Extend the ability of IConfiguration to manage remote configuration through Dcc. + +```c# +IConfiguration +โ”œโ”€โ”€ Local Local node (fixed) +โ”œโ”€โ”€ ConfigurationAPI Remote node (fixed Dcc to expand its capacity) +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms Custom node +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name Parameter Name +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary Dictionary (fixed) The type of Text in DCC is mounted here +``` + +Example๏ผš + +```C# +Install-Package MASA.Contrib.Configuration +Install-Package MASA.Contrib.BasicAbility.Dcc //Provides the ability to remotely configure +``` + +appsettings.json +``` +{ + //Dcc configuration, extended Configuration capabilities, support remote configuration + "DccOptions": { + "ManageServiceAddress": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "Environment": "Development", + "ConfigObjects": [ "Platforms" ], //The name of the object to be mounted, the Platforms configuration will be mounted here under the ConfigurationAPI: node + "Secret": "", //Dcc App key + "Cluster": "Default" +} + +``` + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + configurationBuilder.UseDcc(builder.Services);//Use Dcc + + options.Mapping(SectionTypes.Local, "Appsettings", ""); //Map CustomDccSectionOptions to the Appsettings node under Local +}); + +/// +/// Automatically map node relationships +/// +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; + + [JsonIgnore] + public virtual string? ParentSection { get; init; } = "AppId"; + + [JsonIgnore] + public virtual string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +public class CustomDccSectionOptions +{ + /// + /// The environment name. + /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty + /// + public string? Environment { get; set; } = null; + + /// + /// The cluster name. + /// + public string? Cluster { get; set; } + + /// + /// The app id. + /// + public string AppId { get; set; } = default!; + + public List ConfigObjects { get; set; } = default!; + + public string? Secret { get; set; } +} +``` + +How to use configuration๏ผš + +```c# +var app = builder.Build(); + +app.MapGet("/GetPlatform", ([FromServices] IOptions option) => +{ + //recommend + return System.Text.Json.JsonSerializer.Serialize(option.Value);//Or use IOptionsMonitor to support monitoring changes +}); + +app.MapGet("/GetPlatformByMonitor", ([FromServices] IOptionsMonitor options) => +{ + options.OnChange(option => + { + //TODO Configuration update + }); + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +}); + +app.MapGet("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //Format ConfigurationAPI::: + return configuration["ConfigurationAPI::Platforms:Name"]; +}); + +app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, + [FromServices] IOptions configuration, + PlatformOptions newPlatform) => +{ + //Modify Dcc configuration + return configurationAPIManage.UpdateAsync(option.Value.Environment, + option.Value.Cluster, + option.Value.AppId, + "",newPlatform);//Here Replace-With-Your-ConfigObject is Platforms +}); +app.Run(); +``` + +How to update the configuration: + +```c# +var app = builder.Build(); + +app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, + [FromServices] IOptions configuration, + PlatformOptions newPlatform) => +{ + //Modify Dcc configuration + return configurationAPIManage.UpdateAsync(option.Value.Environment, + option.Value.Cluster, + option.Value.AppId, + "" + ,newPlatform); + //Here Replace-With-Your-ConfigObject is Platforms +}); + +app.Run(); +``` + +Summarize๏ผš + +Dcc provides remote configuration management and viewing capabilities for IConfiguration. For the complete capabilities of IConfiguration, please refer to the [document](../../Configuration/MASA.Contrib.Configuration/README.md) + +Platforms here is remote configuration, which introduces the effect and usage of remote configuration after mounting to IConfiguration. This configuration has nothing to do with Platforms in MASA.Contrib.Configuration. It just shows the use of the same configuration information in two sources. Ways and differences in mapping node relationships \ No newline at end of file diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md new file mode 100644 index 000000000..d55b20ac9 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md @@ -0,0 +1,155 @@ +ไธญ | [EN](README.md) + +## MASA.Contrib.BasicAbility.Dcc + +ไฝœ็”จ: + +้€š่ฟ‡Dccๆ‰ฉๅฑ•IConfiguration็ฎก็†่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ›ใ€‚ + +```c# +IConfiguration +โ”œโ”€โ”€ Local ๆœฌๅœฐ่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ +โ”œโ”€โ”€ ConfigurationAPI ่ฟœ็จ‹่Š‚็‚น๏ผˆๅ›บๅฎš Dccๆ‰ฉๅฑ•ๅ…ถ่ƒฝๅŠ›๏ผ‰ +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰่Š‚็‚น +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary ๅญ—ๅ…ธ๏ผˆๅ›บๅฎš๏ผ‰DCCไธญ็ฑปๅž‹ไธบText็š„ๆŒ‚่ฝฝๅˆฐๆญคๅค„ +``` + +็”จไพ‹๏ผš + +```C# +Install-Package MASA.Contrib.Configuration +Install-Package MASA.Contrib.BasicAbility.Dcc //ๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› +``` + +appsettings.json +``` +{ + //Dcc้…็ฝฎ๏ผŒๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ + "DccOptions": { + "ManageServiceAddress ": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "Environment": "Development", + "ConfigObjects": [ "Platforms" ], //ๅพ…ๆŒ‚่ฝฝ็š„ๅฏน่ฑกๅ, ๆญคๅค„ไผšๅฐ†Platforms้…็ฝฎๆŒ‚่ฝฝๅˆฐConfigurationAPI:่Š‚็‚นไธ‹ + "Secret": "", //Dcc App ็ง˜้’ฅ + "Cluster": "Default" +} + +``` + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDccๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› + + options.Mapping(SectionTypes.Local, "Appsettings", ""); //ๅฐ†CustomDccSectionOptionsๆ˜ ๅฐ„ๅˆฐLocalไธ‹็š„Appsettings่Š‚็‚น +}); + +/// +/// ่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป +/// +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; + + [JsonIgnore] + public virtual string? ParentSection { get; init; } = "Replace-With-Your-AppId"; + + [JsonIgnore] + public virtual string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +public class CustomDccSectionOptions +{ + /// + /// The environment name. + /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty + /// + public string? Environment { get; set; } = null; + + /// + /// The cluster name. + /// + public string? Cluster { get; set; } + + /// + /// The app id. + /// + public string AppId { get; set; } = default!; + + public List ConfigObjects { get; set; } = default!; + + public string? Secret { get; set; } +} +``` + +ๅฆ‚ไฝ•ไฝฟ็”จ้…็ฝฎ๏ผš + +```c# +var app = builder.Build(); + +app.MapGet("/GetPlatform", ([FromServices] IOptions option) => +{ + //ๆŽจ่ + return System.Text.Json.JsonSerializer.Serialize(option.Value); +}); + +app.MapGet("/GetPlatformByMonitor", ([FromServices] IOptionsMonitor options) => +{ + options.OnChange(option => + { + //TODO ้…็ฝฎๆ›ดๆ–ฐ + }); + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +}); + +app.MapGet("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //ๆ ผๅผ๏ผšConfigurationAPI::: + return configuration["ConfigurationAPI::Platforms:Name"]; +}); + +app.Run(); +``` + +ๅฆ‚ไฝ•ๆ›ดๆ–ฐ้…็ฝฎ + + +```c# +var app = builder.Build(); + +app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, + [FromServices] IOptions configuration, + PlatformOptions newPlatform) => +{ + //ไฟฎๆ”นDcc้…็ฝฎ + return configurationAPIManage.UpdateAsync(option.Value.Environment, + option.Value.Cluster, + option.Value.AppId, + "" + ,newPlatform); + //ๆญคๅค„Replace-With-Your-ConfigObjectๆ˜ฏPlatforms +}); + +app.Run(); +``` + +ๆ€ป็ป“๏ผš + +DccไธบIConfigurationๆไพ›ไบ†่ฟœ็จ‹้…็ฝฎ็š„็ฎก็†ไปฅๅŠๆŸฅ็œ‹่ƒฝๅŠ›๏ผŒIConfigurationๅฎŒๆ•ด็š„่ƒฝๅŠ›่ฏทๆŸฅ็œ‹[ๆ–‡ๆกฃ](../../Configuration/MASA.Contrib.Configuration/README.zh-CN.md) + +ๆญคๅค„Platformsไธบ่ฟœ็จ‹้…็ฝฎ๏ผŒไป‹็ป็š„ๆ˜ฏ่ฟœ็จ‹้…็ฝฎๆŒ‚่ฝฝๅˆฐIConfigurationไน‹ๅŽ็š„ๆ•ˆๆžœไปฅๅŠ็”จๆณ•๏ผŒๆญค้…็ฝฎไธŽMASA.Contrib.ConfigurationไธญPlatforms็š„ๆฏซๆ— ๅ…ณ็ณป๏ผŒไป…ไป…ๆ˜ฏๅฑ•็คบๅŒไธ€ไธช้…็ฝฎไฟกๆฏๅœจไธคไธชๆบ็š„ไฝฟ็”จๆ–นๅผไปฅๅŠๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป็š„ๅทฎๅˆซ \ No newline at end of file diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs new file mode 100644 index 000000000..a25cf5516 --- /dev/null +++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs @@ -0,0 +1,24 @@ +global using MASA.BuildingBlocks.Configuration; +global using MASA.Contrib.BasicAbility.Dcc.Internal; +global using MASA.Contrib.BasicAbility.Dcc.Internal.Model; +global using MASA.Contrib.BasicAbility.Dcc.Internal.Parser; +global using MASA.Contrib.BasicAbility.Dcc.Options; +global using MASA.Utils.Caching.Core.DependencyInjection; +global using MASA.Utils.Caching.Core.Models; +global using MASA.Utils.Caching.DistributedMemory.DependencyInjection; +global using MASA.Utils.Caching.DistributedMemory.Interfaces; +global using MASA.Utils.Caching.Redis.DependencyInjection; +global using MASA.Utils.Caching.Redis.Extensions; +global using MASA.Utils.Caching.Redis.Models; +global using MASA.Utils.Caller.Core; +global using MASA.Utils.Caller.HttpClient; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using System.Collections.Concurrent; +global using System.Diagnostics; +global using System.Dynamic; +global using System.Text.Json; +global using static MASA.Contrib.BasicAbility.Dcc.Internal.Constants; + diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks new file mode 160000 index 000000000..af4058aed --- /dev/null +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -0,0 +1 @@ +Subproject commit af4058aede124ed29f82d57b903527b6e2ee4ab5 diff --git a/src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs b/src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs new file mode 100644 index 000000000..8bd2b27a0 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs @@ -0,0 +1,71 @@ +namespace MASA.Contrib.Configuration; + +internal class LocalMasaConfigurationRepository : AbstractConfigurationRepository +{ + public override SectionTypes SectionType { get; init; } + + private ConcurrentDictionary _data = new(); + + public LocalMasaConfigurationRepository( + Dictionary sectionRelation, + ILoggerFactory? loggerFactory) + : base(loggerFactory) + { + this.SectionType = SectionTypes.Local; + foreach (var section in sectionRelation) + { + Initialize(section.Key, section.Value); + + ChangeToken.OnChange(() => section.Value.GetReloadToken(), () => + { + Initialize(section.Key, section.Value); + base.FireRepositoryChange(SectionType, Load()); + }); + } + } + + private void Initialize(string rootSectionName, IConfiguration configuration) + { + Dictionary data = new(); + GetData(rootSectionName, configuration, configuration.GetChildren(), ref data); + var properties = new Properties(data); + _data[rootSectionName] = properties; + } + + private void GetData(string rootSectionName, IConfiguration configuration, IEnumerable configurationSections, ref Dictionary dictionary) + { + foreach (var configurationSection in configurationSections) + { + var section = configuration.GetSection(configurationSection.Path); + + var childrenSections = section.GetChildren(); + + if (!section.Exists() || !childrenSections.Any()) + { + var key = string.IsNullOrEmpty(rootSectionName) ? section.Path : $"{rootSectionName}{ConfigurationPath.KeyDelimiter}{section.Path}"; + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, configuration[section.Path]); + } + } + else + { + GetData(rootSectionName, configuration, childrenSections, ref dictionary); + } + } + } + + public override Properties Load() + { + Dictionary localProperties = new(); + foreach (var item in _data) + { + foreach (var key in item.Value.GetPropertyNames()) + { + localProperties[key] = item.Value.GetProperty(key) ?? string.Empty; + } + } + return new Properties(localProperties); + } +} + diff --git a/src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj b/src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj new file mode 100644 index 000000000..d231705d6 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj @@ -0,0 +1,17 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs new file mode 100644 index 000000000..fd8294c4a --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs @@ -0,0 +1,52 @@ +namespace MASA.Contrib.Configuration; + +public class MasaConfigurationBuilder : IMasaConfigurationBuilder +{ + private readonly IConfigurationBuilder _builder; + + public IDictionary Properties => _builder.Properties; + + public IList Sources => _builder.Sources; + + public Dictionary GetSectionRelations() => SectionRelations; + + internal Dictionary SectionRelations { get; } = new(); + + internal List Repositories { get; } = new(); + + internal List Relations { get; } = new(); + + public MasaConfigurationBuilder(IConfigurationBuilder builder) + => _builder = builder; + + /// + /// + /// + /// + /// If section is null, it is mounted to the Local section + public void AddSection(IConfigurationBuilder configurationBuilder, string? sectionName = null) + { + if (configurationBuilder == null) + throw new ArgumentNullException(nameof(configurationBuilder)); + + if (configurationBuilder.Sources.Count == 0) + throw new ArgumentException("Source cannot be empty"); + + sectionName = sectionName ?? ""; + + if (SectionRelations.ContainsKey(sectionName)) + throw new ArgumentException("Section already exists", nameof(sectionName)); + + SectionRelations.Add(sectionName, configurationBuilder.Build()); + } + + public void AddRepository(IConfigurationRepository configurationRepository) + => Repositories.Add(configurationRepository); + + public void AddRelations(params ConfigurationRelationOptions[] relationOptions) + => Relations.AddRange(relationOptions); + + public IConfigurationBuilder Add(IConfigurationSource source) => _builder.Add(source); + + public IConfigurationRoot Build() => _builder.Build(); +} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs new file mode 100644 index 000000000..09552cf5c --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs @@ -0,0 +1,35 @@ +namespace MASA.Contrib.Configuration; + +public static class MasaConfigurationExtensions +{ + public static void UseMasaOptions(this IMasaConfigurationBuilder builder, Action options) + { + var relation = new MasaRelationOptions(); + options.Invoke(relation); + builder.AddRelations(relation.Relations.ToArray()); + } + + internal static void AutoMapping(this MasaConfigurationBuilder builder, params Assembly[] assemblies) + { + var optionTypes = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type != typeof(IMasaConfigurationOptions) && type != typeof(MasaConfigurationOptions) && typeof(IMasaConfigurationOptions).IsAssignableFrom(type)) + .ToList(); + optionTypes.ForEach(optionType => + { + var option = (IMasaConfigurationOptions)Activator.CreateInstance(optionType)!; + var sectionName = option.Section ?? optionType.Name; + if (builder.Relations.Any(relation => relation.SectionType == option.SectionType && relation.Section == sectionName)) + { + throw new ArgumentException("The section has been loaded, no need to load repeatedly, check whether there are duplicate sections or inheritance between auto-mapping classes"); + } + builder.AddRelations(new ConfigurationRelationOptions() + { + SectionType = option.SectionType, + ParentSection = option.ParentSection, + Section = sectionName, + ObjectType = optionType + }); + }); + } +} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs new file mode 100644 index 000000000..09e2fa6a7 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs @@ -0,0 +1,19 @@ +namespace MASA.Contrib.Configuration; + +public abstract class MasaConfigurationOptions : IMasaConfigurationOptions +{ + /// + /// The name of the parent section, if it is empty, it will be mounted under SectionType, otherwise it will be mounted to the specified section under SectionType + /// + [JsonIgnore] + public virtual string? ParentSection { get; init; } = null; + + /// + /// The section null means same as the class name, else load from the specify section + /// + [JsonIgnore] + public virtual string? Section { get; init; } = null; + + [JsonIgnore] + public abstract SectionTypes SectionType { get; init; } +} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs new file mode 100644 index 000000000..f4ba2b5cb --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs @@ -0,0 +1,64 @@ +namespace MASA.Contrib.Configuration; + +public class MasaConfigurationProvider : ConfigurationProvider, IRepositoryChangeListener, IDisposable +{ + private readonly ConcurrentDictionary _data; + private readonly IEnumerable _configurationRepositories; + + public MasaConfigurationProvider(MasaConfigurationSource source) + { + _data = new(); + _configurationRepositories = source.Builder.Repositories; + + foreach (var configurationRepository in _configurationRepositories) + { + configurationRepository.AddChangeListener(this); + } + } + + public override void Load() + { + foreach (var configurationRepository in _configurationRepositories) + { + var properties = configurationRepository.Load(); + _data[configurationRepository.SectionType] = properties; + } + SetData(); + } + + public void OnRepositoryChange(SectionTypes sectionType, Properties newProperties) + { + if (_data[sectionType] == newProperties) + return; + + _data[sectionType] = newProperties; + + SetData(); + + OnReload(); + } + + void SetData() + { + Dictionary data = new(); + + foreach (var configurationType in _data.Keys) + { + var properties = _data[configurationType]; + foreach (var key in properties.GetPropertyNames()) + { + data[$"{configurationType}{ConfigurationPath.KeyDelimiter}{key}"] = properties.GetProperty(key)!; + } + } + + Data = data; + } + + public void Dispose() + { + foreach (var configurationRepository in _configurationRepositories) + { + configurationRepository.RemoveChangeListener(this); + } + } +} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs new file mode 100644 index 000000000..7f57cf17e --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs @@ -0,0 +1,17 @@ +namespace MASA.Contrib.Configuration; + +public class MasaConfigurationSource : IConfigurationSource +{ + internal MasaConfigurationBuilder Builder; + + public MasaConfigurationSource(MasaConfigurationBuilder builder) + { + Builder = builder; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new MasaConfigurationProvider(this); + } +} + diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs b/src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs new file mode 100644 index 000000000..b521c4efa --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs @@ -0,0 +1,33 @@ +namespace MASA.Contrib.Configuration; + +public class MasaRelationOptions +{ + internal List Relations { get; } = new(); + + /// + /// Map Section relationship + /// + /// + /// + /// parent section, local section is the name of the locally configured section, and ConfigurationAPI is the name of the Appid where the configuration is located + /// The default is null, which is consistent with the mapping class name + /// + /// + public MasaRelationOptions Mapping(SectionTypes sectionType, string parentSection, string? section = null) + { + if (section == null) + section = typeof(TModel).Name; + + if (Relations.Any(relation => relation.SectionType == sectionType && relation.Section == section)) + throw new ArgumentOutOfRangeException(nameof(section), "The current section already has a configuration"); + + Relations.Add(new ConfigurationRelationOptions() + { + SectionType = sectionType, + ParentSection = parentSection, + Section = section, + ObjectType = typeof(TModel) + }); + return this; + } +} diff --git a/src/Configuration/MASA.Contrib.Configuration/README.md b/src/Configuration/MASA.Contrib.Configuration/README.md new file mode 100644 index 000000000..921dd0058 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/README.md @@ -0,0 +1,135 @@ +[ไธญ](README.zh-CN.md) | EN + +## MASA.Contrib.Configuration + +Structure: + +```c# +IConfiguration +โ”œโ”€โ”€ Local Local node (fixed) +โ”‚ โ”œโ”€โ”€ Appsettings Default local node +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ Platforms Custom configuration +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ โ”œโ”€โ”€ Name Parameter name +โ”œโ”€โ”€ ConfigurationAPI Remote node (fixed) +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms Custom node +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name Parameter name +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary Dictionary (fixed) +``` + +Example๏ผš + +```C# +Install-Package MASA.Contrib.Configuration +Install-Package MASA.Contrib.BasicAbility.Dcc //DCC can provide remote configuration capabilities +```json +{ + //Custom configuration + "Platforms": { + "Name": "Masa.Demo" + }, + //Dcc configuration, extended Configuration capabilities, support remote configuration + "DccOptions": { + "ManageServiceAddress": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "ConfigObjects": [ "Platforms" ], //The name of the object to be mounted. Here, the Platforms configuration will be mounted under the ConfigurationAPI: node + "Secret": "", //Dcc App key + "Cluster": "Default" +} +``` + +Automatically map node relationships๏ผš + +```c# +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +//Use MasaConfiguration to take over Configuration, and mount the current Configuration to Local:Appsettings section by default +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//Use Dcc to extend Configuration capabilities and support remote configuration +}); +``` + +Or manually map node relationships๏ผš + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//Use Dcc to extend Configuration capabilities and support remote configuration + + configurationBuilder.UseMasaOptions(options => + { + options.Mapping(SectionTypes.Local, "Appsettings", "Platforms"); //Map the PlatformOptions binding to the Local:Appsettings:Platforms node + }); +}); +``` + +how to use๏ผš + +```c# +var app = builder.Build(); + +app.Map("/GetPlatform", ([FromServices] IOptions option) => +{ + //Recommended (need to automatically or manually map the node relationship before it can be used) + return System.Text.Json.JsonSerializer.Serialize(option.Value); +}); + +app.Map("/GetPlatform", ([FromServices] IOptionsMonitor option) => +{ + //Recommended (need to automatically or manually map the node relationship before it can be used) + options.OnChange(option => + { + //TODO Configuration update service + }); + + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +}); + +app.Map("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //Base + return configuration["Local:Appsettings:Platforms:Name"]; +}); + +app.Run(); +``` + +How to take over more local nodes๏ผŸ + +```c# +builder.AddMasaConfiguration(builder =>{ + + builder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("custom.json", true, true), "Custom");//Mount the custom.json configuration under the Local:Custom node +}); +``` + +Tip๏ผš + +Configuration automatically obtains classes that inherit the IMasaConfigurationOptions interface by default, and maps the node relationship to facilitate obtaining configuration information through IOptions, IOptionsSnapshot, and IOptionsMonitor + +The above Platforms is a local configuration, used to demonstrate the effect and usage of the local configuration after being mounted to IConfiguration \ No newline at end of file diff --git a/src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md b/src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md new file mode 100644 index 000000000..0bd1a1830 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md @@ -0,0 +1,140 @@ +ไธญ | [EN](README.md) + +## MASA.Contrib.Configuration + +็ป“ๆž„: + +```c# +IConfiguration +โ”œโ”€โ”€ Local ๆœฌๅœฐ่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ +โ”‚ โ”œโ”€โ”€ Appsettings ้ป˜่ฎคๆœฌๅœฐ่Š‚็‚น +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰้…็ฝฎ +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ +โ”œโ”€โ”€ ConfigurationAPI ่ฟœ็จ‹่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰่Š‚็‚น +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary ๅญ—ๅ…ธ๏ผˆๅ›บๅฎš๏ผ‰ +``` + +็”จไพ‹๏ผš + +```C# +Install-Package MASA.Contrib.Configuration +Install-Package MASA.Contrib.BasicAbility.Dcc //DCCๅฏๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› +``` + +appsettings.json +```json +{ + //่‡ชๅฎšไน‰้…็ฝฎ + "Platforms": { + "Name": "Masa.Demo" + }, + //Dcc้…็ฝฎ๏ผŒๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ + "DccOptions": { + "ManageServiceAddress ": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "ConfigObjects": [ "Platforms" ], // ่ฆๆŒ‚่ฝฝ็š„ๅฏน่ฑกๅ็งฐ,ๆญคๅค„ไผšๅฐ†Platforms้…็ฝฎๆŒ‚่ฝฝๅˆฐConfigurationAPI:่Š‚็‚นไธ‹ + "Secret": "", //Dcc App ็ง˜้’ฅ + "Cluster": "Default" +} +``` + +่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผš + +```c# +/// +/// ่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป +/// +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +//ไฝฟ็”จMasaConfigurationๆŽฅ็ฎกConfiguration๏ผŒ้ป˜่ฎคไผšๅฐ†ๅฝ“ๅ‰็š„ConfigurationๆŒ‚่ฝฝๅˆฐLocal:Appsettings่Š‚็‚น +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDcc ๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ +}); +``` + +ๆˆ–ๆ‰‹ๅŠจๆทปๅŠ ๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผš + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDcc ๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ + + configurationBuilder.UseMasaOptions(options => + { + options.Mapping(SectionTypes.Local, "Appsettings", "Platforms"); //ๅฐ†PlatformOptions็ป‘ๅฎšๆ˜ ๅฐ„ๅˆฐLocal:Appsettings:Platforms่Š‚็‚น + }); +}); +``` + +ๅฆ‚ไฝ•ไฝฟ็”จ๏ผš + +```c# +var app = builder.Build(); + +app.Map("/GetPlatform", ([FromServices] IOptions option) => +{ + //ๆŽจ่๏ผˆ้œ€่ฆ่‡ชๅŠจๆˆ–ๆ‰‹ๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณปๅŽๆ‰่ƒฝไฝฟ็”จ๏ผ‰ + return System.Text.Json.JsonSerializer.Serialize(option.Value); +}); + +app.Map("/GetPlatform", ([FromServices] IOptionsMonitor option) => +{ + options.OnChange(option => + { + //TODO ้…็ฝฎๆ›ดๆ–ฐไธšๅŠก + }); + + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +});//ๆŽจ่๏ผˆ้œ€่ฆ่‡ชๅŠจๆˆ–ๆ‰‹ๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณปๅŽๆ‰่ƒฝไฝฟ็”จ๏ผ‰ + +app.Map("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //ๅŸบ็ก€ + return configuration["Local:Appsettings:Platforms:Name"]; +}); + +app.Run(); +``` + +ๅฆ‚ไฝ•ๆŽฅ็ฎกๆ›ดๅคš็š„ๆœฌๅœฐ่Š‚็‚น๏ผŸ + +```c# +builder.AddMasaConfiguration(builder =>{ + + builder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("custom.json", true, true), "Custom");//ๅฐ†custom.json้…็ฝฎๆŒ‚่ฝฝๅˆฐLocal:Custom่Š‚็‚นไธ‹ +}); +``` + +ๆ็คบ๏ผš + +Configuration้ป˜่ฎค่‡ชๅŠจ่Žทๅ–็ปงๆ‰ฟIMasaConfigurationOptionsๆŽฅๅฃ็š„็ฑป๏ผŒๅนถๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผŒๆ–นไพฟ้€š่ฟ‡IOptionsใ€IOptionsSnapshotใ€IOptionsMonitor่Žทๅ–้…็ฝฎไฟกๆฏ + +ไธŠๆ–‡Platformsไธบๆœฌๅœฐ้…็ฝฎ๏ผŒ็”จไบŽๆผ”็คบๆœฌๅœฐ้…็ฝฎๆŒ‚่ฝฝๅˆฐIConfigurationๅŽ็š„ๆ•ˆๆžœไปฅๅŠไฝฟ็”จ็”จๆณ• \ No newline at end of file diff --git a/src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs b/src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..b06ea09b4 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs @@ -0,0 +1,138 @@ +namespace MASA.Contrib.Configuration; + +public static class ServiceCollectionExtensions +{ + public static WebApplicationBuilder AddMasaConfiguration( + this WebApplicationBuilder builder, + Action? configureDelegate = null) + => builder.AddMasaConfiguration(configureDelegate, + "Appsettings", + AppDomain.CurrentDomain.GetAssemblies()); + + public static WebApplicationBuilder AddMasaConfiguration( + this WebApplicationBuilder builder, + Action? configureDelegate, + string defaultSectionName = "Appsettings", + params Assembly[] assemblies) + { + var configurationBuilder = GetConfigurationBuilder(builder.Configuration); + + IConfigurationRoot masaConfiguration = builder.Services.CreateMasaConfiguration(configureDelegate, configurationBuilder, defaultSectionName, assemblies); + if (!masaConfiguration.Providers.Any()) + return builder; + + Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.ConfigureAppConfiguration(builder.Host, configBuilder => + { + configBuilder.Sources.Clear(); + }); + builder.Configuration.AddConfiguration(masaConfiguration); + + return builder; + } + + public static IConfigurationRoot CreateMasaConfiguration( + this IServiceCollection services, + Action? configureDelegate, + IConfigurationBuilder? configurationBuilder = null, + string defaultSectionName = "Appsettings", + params Assembly[] assemblies) + { + if (services.Any(service => service.ImplementationType == typeof(MasaConfigurationProvider))) + return new ConfigurationBuilder().Build(); + + services.AddSingleton(); + services.AddOptions(); + + MasaConfigurationBuilder masaConfigurationBuilder = new MasaConfigurationBuilder(new ConfigurationBuilder()); + if (configurationBuilder != null) + { + masaConfigurationBuilder.AddSection(configurationBuilder, defaultSectionName); + } + configureDelegate?.Invoke(masaConfigurationBuilder); + + if (masaConfigurationBuilder.SectionRelations.Count == 0) + throw new Exception("Please add the section to be loaded"); + + var localConfigurationRepository = new LocalMasaConfigurationRepository(masaConfigurationBuilder.SectionRelations, services.BuildServiceProvider().GetService()); + masaConfigurationBuilder.AddRepository(localConfigurationRepository); + + var source = new MasaConfigurationSource(masaConfigurationBuilder); + var configuration = masaConfigurationBuilder.Add(source).Build(); + + masaConfigurationBuilder.AutoMapping(assemblies); + masaConfigurationBuilder.Relations.ForEach(relation => + { + List sectionNames = new List() + { + relation.SectionType.ToString(), + }; + if (!string.IsNullOrEmpty(relation.ParentSection)) + sectionNames.Add(relation.ParentSection); + + if (relation.Section != "") + { + sectionNames.AddRange(relation.Section.Split(ConfigurationPath.KeyDelimiter)); + } + + services.ConfigureOption(configuration, sectionNames, relation.ObjectType); + }); + + return configuration; + } + + private static IConfigurationBuilder GetConfigurationBuilder(ConfigurationManager configuration) + { + var configurationBuilder = new ConfigurationBuilder(); + foreach (var source in ((IConfigurationBuilder)configuration).Sources) + { + configurationBuilder.Add(source); + } + return configurationBuilder; + } + + private static void ClearSource(this WebApplicationBuilder builder) + { + Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.ConfigureAppConfiguration(builder.Host, configBuilder => + { + configBuilder.Sources.Clear(); + }); + } + + internal static void ConfigureOption( + this IServiceCollection services, + IConfiguration configuration, + List sectionNames, Type optionType) + { + IConfigurationSection? configurationSection = null; + foreach (var sectionName in sectionNames) + { + if (configurationSection == null) + configurationSection = configuration.GetSection(sectionName); + else + configurationSection = configurationSection.GetSection(sectionName); + } + if (!configurationSection.Exists()) + { + throw new ArgumentNullException("Section", "Check if the mapping section is correct"); + } + + var configurationChangeTokenSource = + Activator.CreateInstance(typeof(ConfigurationChangeTokenSource<>).MakeGenericType(optionType), string.Empty, + configurationSection)!; + services.TryAdd(new ServiceDescriptor(typeof(IOptionsChangeTokenSource<>).MakeGenericType(optionType), + configurationChangeTokenSource)); + + Action configureBinder = _ => { }; + var configureOptions = + Activator.CreateInstance(typeof(NamedConfigureFromConfigurationOptions<>).MakeGenericType(optionType), + string.Empty, + configurationSection, configureBinder)!; + services.TryAdd(new ServiceDescriptor(typeof(IConfigureOptions<>).MakeGenericType(optionType), + configureOptions)); + } + + private class MasaConfigurationProvider + { + + } +} diff --git a/src/Configuration/MASA.Contrib.Configuration/_Imports.cs b/src/Configuration/MASA.Contrib.Configuration/_Imports.cs new file mode 100644 index 000000000..85d4ce738 --- /dev/null +++ b/src/Configuration/MASA.Contrib.Configuration/_Imports.cs @@ -0,0 +1,15 @@ +global using MASA.BuildingBlocks.Configuration; +global using MASA.BuildingBlocks.Configuration.Options; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using Microsoft.Extensions.Primitives; +global using System; +global using System.Collections.Concurrent; +global using System.Collections.Generic; +global using System.Linq; +global using System.Reflection; +global using System.Text.Json.Serialization; diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs index fe1608028..ee5ae368f 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs @@ -13,17 +13,15 @@ public static IDispatcherOptions UseRepository( where TDbContext : DbContext { if (options.Services == null) - { throw new ArgumentNullException(nameof(options.Services)); - } - if (options.Services.Any(service => service.ImplementationType == typeof(RepositoryProvider))) return options; + if (options.Services.Any(service => service.ImplementationType == typeof(RepositoryProvider))) + return options; + options.Services.AddSingleton(); if (options.Services.All(service => service.ServiceType != typeof(IUnitOfWork))) - { throw new Exception("Please add UoW first."); - } options.Services.TryAddRepository(assemblies); return options; diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs new file mode 100644 index 000000000..b47f613b9 --- /dev/null +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs @@ -0,0 +1,93 @@ +namespace MASA.Contrib.DDD.Domain.Repository.EF.Internal; + +internal static class LinqExtensions +{ + public static IQueryable GetQueryable(this IQueryable query, Dictionary fields) where TEntity : class + { + foreach (var field in fields) + { + query = query.GetQueryable(field.Key, field.Value); + } + return query; + } + + private static IQueryable GetQueryable(this IQueryable query, string field, object val) where TEntity : class + { + Type type = typeof(TEntity); + var parameter = Expression.Parameter(type, "entity"); + + PropertyInfo property = type.GetProperty(field)!; + Expression expProperty = Expression.Property(parameter, property.Name); + + Expression> valueLamda = () => val; + Expression expValue = Expression.Convert(valueLamda.Body, property.PropertyType); + Expression expression = Expression.Equal(expProperty, expValue); + Expression> filter = (Expression>)Expression.Lambda(expression, parameter); + return query.Where(filter); + } + + public static IQueryable OrderBy(this IQueryable query, Dictionary fields) where TEntity : class + { + var index = 0; + foreach (var field in fields) + { + if (index == 0) + query = query.OrderBy(field.Key, field.Value); + else + query = query.ThenBy(field.Key, field.Value); + index++; + } + + return query; + } + + private static IQueryable OrderBy(this IQueryable query, string field, bool desc) where TEntity : class + { + ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity)); + Expression key = Expression.Property(parameterExpression, field); + var propertyInfo = GetPropertyInfo(typeof(TEntity), field); + var orderExpression = GetOrderExpression(typeof(TEntity), propertyInfo); + if (desc) + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(TEntity), propertyInfo.PropertyType); + return (genericMethod.Invoke(null, new object[] { query, orderExpression }) as IQueryable)!; + } + else + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(TEntity), propertyInfo.PropertyType); + return (IQueryable)genericMethod.Invoke(null, new object[] { query, orderExpression })!; + } + } + + private static IQueryable ThenBy(this IQueryable query, string field, bool desc) where T : class + { + ParameterExpression parameterExpression = Expression.Parameter(typeof(T)); + Expression key = Expression.Property(parameterExpression, field); + var propertyInfo = GetPropertyInfo(typeof(T), field); + var orderExpression = GetOrderExpression(typeof(T), propertyInfo); + if (desc) + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "ThenByDescending" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType); + return (genericMethod.Invoke(null, new object[] { query, orderExpression }) as IQueryable)!; + } + else + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "ThenBy" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType); + return (IQueryable)genericMethod.Invoke(null, new object[] { query, orderExpression })!; + } + } + + private static PropertyInfo GetPropertyInfo(Type entityType, string field) + => entityType.GetProperties().FirstOrDefault(p => p.Name.Equals(field, StringComparison.OrdinalIgnoreCase))!; + + private static LambdaExpression GetOrderExpression(Type entityType, PropertyInfo propertyInfo) + { + var parametersExpression = Expression.Parameter(entityType); + var fieldExpression = Expression.PropertyOrField(parametersExpression, propertyInfo.Name); + return Expression.Lambda(fieldExpression, parametersExpression); + } +} diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs index 4a344663f..f009c3c8f 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs @@ -2,6 +2,11 @@ namespace MASA.Contrib.DDD.Domain.Repository.EF.Internal; internal static class ServiceCollectionRepositoryExtensions { + /// + /// The relationship between entity and keys + /// + public static Dictionary Relations = new(); + public static IServiceCollection TryAddRepository( this IServiceCollection services, params Assembly[] assemblies) @@ -9,7 +14,7 @@ public static IServiceCollection TryAddRepository( { if (assemblies == null || assemblies.Length == 0) { - assemblies = AppDomain.CurrentDomain.GetAssemblies(); + throw new ArgumentNullException(nameof(assemblies)); } var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()); @@ -21,10 +26,53 @@ public static IServiceCollection TryAddRepository( services.TryAddAddDefaultRepository(repositoryInterfaceType, GetRepositoryImplementationType(typeof(TDbContext), entityType)); services.TryAddCustomRepository(repositoryInterfaceType, allTypes.ToArray()); + + var keys = GetKeys(entityType); + CheckKeys(entityType, keys); + Relations.TryAdd(entityType, keys); } + return services; } + private static string[] GetKeys(Type entityType) + { + IAggregateRoot aggregateRoot; + try + { + var constructorInfo = entityType + .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .FirstOrDefault(con => !con.CustomAttributes.Any()); + + if (constructorInfo == null) + throw new ArgumentNullException("The entity needs to have an empty constructor"); + + aggregateRoot = (IAggregateRoot)Activator.CreateInstance(entityType, constructorInfo.IsPrivate)!; + } + catch (Exception) + { + throw new ArgumentNullException("The entity needs to have an empty constructor"); + } + + var keys = aggregateRoot.GetKeys().Select(k => k.Name).ToArray(); + if (keys.Length != keys.Where(key => !string.IsNullOrEmpty(key)).Distinct().Count()) + throw new ArgumentException("The joint primary key cannot be empty"); + + return keys; + } + + /// + /// Check if the combined primary key is correct + /// + private static void CheckKeys(Type entityType, string[] fields) + { + foreach (var field in fields) + { + if (!entityType.GetProperties().Any(p => p.Name.Equals(field, StringComparison.OrdinalIgnoreCase))!) + throw new ArgumentException("Check if the combined primary key is correct"); + } + } + private static bool IsAggregateRootEntity(this Type type) => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && type != typeof(Entity) && typeof(IAggregateRoot).IsAssignableFrom(type); diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj index 2e7fffff8..679608d24 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj @@ -5,11 +5,14 @@ enable enable - + - - - + + - + + + + + diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md index a9829bb4e..54dc8f883 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md @@ -1,3 +1,5 @@ +[ไธญ](README.zh-CN.md) | EN + ## MASA.Contrib.DDD.Domain.Repository.EF Example๏ผš @@ -14,7 +16,7 @@ Install-Package MASA.Contrib.DDD.Domain.Repository.EF builder.Services .AddDomainEventBus(options => { - options.UseRepository();//Use the EF version of Repository to achieve + options.UseRepository();//Use the EF version of Repository to achieve } ``` @@ -51,7 +53,7 @@ public interface IProductRepository : IRepository public class ProductRepository : Repository, IProductRepository { - public Task> ItemsWithNameAsync(string name) + public Task> ItemsWithNameAsync(string name) { //Todo } diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-cn.md b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md similarity index 88% rename from src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-cn.md rename to src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md index 84d376759..3a9cf09c5 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-cn.md +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md @@ -1,3 +1,5 @@ +ไธญ | [EN](README.md) + ## MASA.Contrib.DDD.Domain.Repository.EF ็”จไพ‹๏ผš @@ -14,7 +16,7 @@ Install-Package MASA.Contrib.DDD.Domain.Repository.EF builder.Services .AddDomainEventBus(options => { - options.UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ + options.UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ } ``` @@ -30,9 +32,9 @@ public class DemoService : ServiceBase { public CatalogService(IServiceCollection services) : base(services) { - + } - + public async Task CreateProduct(ProductItem product,[FromService]IRepository repository) { await repository.AddAsync(product); @@ -51,7 +53,7 @@ public interface IProductRepository : IRepository public class ProductRepository : Repository, IProductRepository { - public Task> ItemsWithNameAsync(string name) + public Task> ItemsWithNameAsync(string name) { //Todo } diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs index bcde5dbce..bf1894894 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs @@ -12,111 +12,202 @@ public Repository(TDbContext context, IUnitOfWork unitOfWork) UnitOfWork = unitOfWork; } + public override bool TransactionHasBegun + => _context.Database.CurrentTransaction != null; + public override DbTransaction Transaction { get { + if (!UseTransaction) + throw new NotSupportedException(nameof(Transaction)); + if (TransactionHasBegun) - { return _context.Database.CurrentTransaction!.GetDbTransaction(); - } + return _context.Database.BeginTransaction().GetDbTransaction(); } + } + + public override bool UseTransaction + { + get => UnitOfWork.UseTransaction; + set => UnitOfWork.UseTransaction = value; + } + + public override IUnitOfWork UnitOfWork { get; } + + public override EntityState EntityState + { + get => UnitOfWork.EntityState; set { - if (_context.Database.CurrentTransaction == null) - { - _context.Database.UseTransaction(value); - } - else - { - throw new NotSupportedException("The transaction is opened"); - } + UnitOfWork.EntityState = value; + if (value == EntityState.Changed) + CheckAndOpenTransaction(); } } - public override IUnitOfWork UnitOfWork { get; set; } - - public override async ValueTask AddAsync(TEntity entity, CancellationToken cancellationToken = default) - => (await _context.AddAsync(entity, cancellationToken).AsTask()).Entity; + public override async ValueTask AddAsync( + TEntity entity, + CancellationToken cancellationToken = default) + { + var response = (await _context.AddAsync(entity, cancellationToken).AsTask()).Entity; + EntityState = EntityState.Changed; + return response; + } - public override Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) - => _context.AddRangeAsync(entities, cancellationToken); + public override async Task AddRangeAsync( + IEnumerable entities, + CancellationToken cancellationToken = default) + { + await _context.AddRangeAsync(entities, cancellationToken); + EntityState = EntityState.Changed; + } public override Task CommitAsync(CancellationToken cancellationToken = default) => UnitOfWork.CommitAsync(cancellationToken); - public override ValueTask DisposeAsync() => ValueTask.CompletedTask; + public override async ValueTask DisposeAsync() => await _context.DisposeAsync(); + + public override void Dispose() => _context.Dispose(); + + public override Task FindAsync( + object?[]? keyValues, + CancellationToken cancellationToken = default) + { + if (keyValues == null) + return Task.FromResult(default(TEntity?)); + + var keys = GetKeys(typeof(TEntity)); + Dictionary fields = new(); + for (var i = 0; i < keys.Length; i++) + { + fields.Add(keys[i], keyValues[i]!); + } - public override ValueTask FindAsync(object?[]? keyValues, CancellationToken cancellationToken) - => _context.Set().FindAsync(keyValues, cancellationToken); + return _context.Set().IgnoreQueryFilters().GetQueryable(fields).FirstOrDefaultAsync(cancellationToken); + } public override Task FindAsync( Expression> predicate, CancellationToken cancellationToken = default) => _context.Set().Where(predicate).FirstOrDefaultAsync(cancellationToken); - public override Task GetCountAsync(CancellationToken cancellationToken) - => _context.Set().LongCountAsync(cancellationToken); + public override async Task GetCountAsync(CancellationToken cancellationToken = default) + => await _context.Set().LongCountAsync(cancellationToken); public override Task GetCountAsync( Expression> predicate, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) => _context.Set().LongCountAsync(predicate, cancellationToken); - public override async Task> GetListAsync(CancellationToken cancellationToken) + public override async Task> GetListAsync(CancellationToken cancellationToken = default) => await _context.Set().ToListAsync(cancellationToken); public override async Task> GetListAsync( Expression> predicate, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) => await _context.Set().Where(predicate).ToListAsync(cancellationToken); - public override Task> GetPaginatedListAsync(int skip, int take, string? sorting, CancellationToken cancellationToken) + /// + /// + /// + /// + /// + /// asc or desc, default asc + /// + /// + public override Task> GetPaginatedListAsync( + int skip, + int take, + Dictionary? sorting, + CancellationToken cancellationToken = default) { - var iQueryable = _context.Set(); - return (string.Equals(sorting ?? "asc", "asc", StringComparison.CurrentCultureIgnoreCase) ? iQueryable.OrderBy(x => x.GetKeys()) : iQueryable.OrderByDescending(x => x.GetKeys())).Skip(skip).Take(take).ToListAsync(cancellationToken); + sorting ??= new Dictionary(GetKeys(typeof(TEntity)).Select(key => new KeyValuePair(key, false))); + + return _context.Set().OrderBy(sorting).Skip(skip).Take(take).ToListAsync(cancellationToken); } - public override Task> GetPaginatedListAsync(Expression> predicate, int skip, int take, string? sorting, CancellationToken cancellationToken) + /// + /// + /// + /// condition + /// + /// + /// asc or desc, default asc + /// + /// + public override Task> GetPaginatedListAsync( + Expression> predicate, + int skip, + int take, + Dictionary? sorting, + CancellationToken cancellationToken = default) { - var iQueryable = _context.Set().Where(predicate); - return (string.Equals(sorting ?? "asc", "asc", StringComparison.CurrentCultureIgnoreCase) ? iQueryable.OrderBy(x => x.GetKeys()) : iQueryable.OrderByDescending(x => x.GetKeys())).Skip(skip).Take(take).ToListAsync(cancellationToken); + sorting ??= new Dictionary(GetKeys(typeof(TEntity)).Select(key => new KeyValuePair(key, false))); + + return _context.Set().Where(predicate).OrderBy(sorting).Skip(skip).Take(take).ToListAsync(cancellationToken); } public override Task RemoveAsync(TEntity entity, CancellationToken cancellationToken = default) { _context.Set().Remove(entity); + EntityState = EntityState.Changed; return Task.FromResult(entity); } public override async Task RemoveAsync(Expression> predicate, CancellationToken cancellationToken = default) { - var entities = await GetListAsync(predicate, cancellationToken)!; + var entities = await GetListAsync(predicate, cancellationToken); + EntityState = EntityState.Changed; _context.Set().RemoveRange(entities); } public override Task RemoveRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) { _context.Set().RemoveRange(entities); + EntityState = EntityState.Changed; return Task.CompletedTask; } public override Task RollbackAsync(CancellationToken cancellationToken = default) => UnitOfWork.RollbackAsync(cancellationToken); - public override Task SaveChangesAsync(CancellationToken cancellationToken = default) - => UnitOfWork.SaveChangesAsync(cancellationToken); + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + await UnitOfWork.SaveChangesAsync(cancellationToken); + } public override Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) { _context.Set().Update(entity); + EntityState = EntityState.Changed; return Task.FromResult(entity); } public override Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) { _context.Set().UpdateRange(entities); + EntityState = EntityState.Changed; return Task.CompletedTask; } + + /// + /// When additions, deletions and changes are made through the Repository and the transaction is currently allowed and the transaction is not opened, the transaction is started + /// + private void CheckAndOpenTransaction() + { + if (!UnitOfWork.UseTransaction) + return; + + if (!UnitOfWork.TransactionHasBegun) + { + _ = UnitOfWork.Transaction; // Open the transaction + } + CommitState = CommitState.UnCommited; + } + + protected string[] GetKeys(Type entityType) + => ServiceCollectionRepositoryExtensions.Relations[entityType]!; } diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs index 14372d0ed..95dee48cd 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs @@ -1,4 +1,4 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.DDD.Domain.Entities; global using MASA.BuildingBlocks.DDD.Domain.Repositories; global using MASA.BuildingBlocks.Dispatcher.Events; @@ -7,6 +7,9 @@ global using Microsoft.EntityFrameworkCore.Storage; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using System.Collections.Concurrent; global using System.Data.Common; global using System.Linq.Expressions; global using System.Reflection; +global using EntityState = MASA.BuildingBlocks.Data.UoW.EntityState; diff --git a/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs b/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs index f6e6c0822..67fc8ce04 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs @@ -7,9 +7,13 @@ public class DomainEventBus : IDomainEventBus private readonly IUnitOfWork _unitOfWork; private readonly DispatcherOptions _options; - private readonly ConcurrentQueue _eventQueue = new ConcurrentQueue(); + private readonly ConcurrentQueue> _eventQueue = new(); - public DomainEventBus(IEventBus eventBus, IIntegrationEventBus integrationEventBus, IUnitOfWork unitOfWork, IOptions options) + public DomainEventBus( + IEventBus eventBus, + IIntegrationEventBus integrationEventBus, + IUnitOfWork unitOfWork, + IOptions options) { _eventBus = eventBus; _integrationEventBus = integrationEventBus; @@ -19,33 +23,62 @@ public DomainEventBus(IEventBus eventBus, IIntegrationEventBus integrationEventB public async Task PublishAsync(TEvent @event) where TEvent : IEvent { + if (@event is IDomainEvent domainEvent && !IsAssignableFromDomainQuery(@event.GetType())) + { + domainEvent.UnitOfWork = _unitOfWork; + } if (@event is IIntegrationEvent integrationEvent) { - if (integrationEvent.UnitOfWork == null) - { - integrationEvent.UnitOfWork = _unitOfWork; - } - await _integrationEventBus.PublishAsync(integrationEvent); + await _integrationEventBus.PublishAsync((TEvent)integrationEvent); } else { await _eventBus.PublishAsync(@event); } + + bool IsAssignableFromDomainQuery(Type? type) + { + if (type == null) + return false; + + if (!type.IsGenericType) + { + return IsAssignableFromDomainQuery(type.BaseType); + } + return type.GetInterfaces().Any(type => type.GetGenericTypeDefinition() == typeof(IDomainQuery<>)); + } } public Task Enqueue(TDomentEvent @event) where TDomentEvent : IDomainEvent { - _eventQueue.Enqueue(@event); + _eventQueue.Enqueue(new KeyValuePair(@event.GetType(), @event)); return Task.CompletedTask; } public async Task PublishQueueAsync() { - while (_eventQueue.TryDequeue(out IDomainEvent? @event)) + while (_eventQueue.TryDequeue(out KeyValuePair @event)) { - await PublishAsync(@event); + await PublishAsync(@event.Key, @event.Value); } } + private async Task PublishAsync(Type type, TEvent @event) where TEvent : IEvent + { + if (@event is IIntegrationEvent integrationEvent) + { + await PublishAsync(integrationEvent); + } + else + { + var parameters = Convert.ChangeType(@event, type); + var invokeDelegate = InvokeBuilder.Build(_eventBus.GetType(), nameof(_eventBus.PublishAsync), type); + await invokeDelegate.Invoke(_eventBus, parameters); + } + } + + public async Task CommitAsync(CancellationToken cancellationToken = default) + => await _unitOfWork.CommitAsync(cancellationToken); + public IEnumerable GetAllEventTypes() => _options.AllEventTypes.Concat(_eventBus.GetAllEventTypes()).Distinct(); } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs index 9fba0e906..24c351c5d 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs @@ -1,19 +1,15 @@ namespace MASA.Contrib.DDD.Domain.Events; -public record DomainCommand : IDomainCommand +public record DomainCommand(Guid Id, DateTime CreationTime) : IDomainCommand { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; [JsonIgnore] - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } public DomainCommand() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public DomainCommand(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs index af5a97433..2aeb5a3fc 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs @@ -1,19 +1,16 @@ namespace MASA.Contrib.DDD.Domain.Events; -public record DomainEvent : IDomainEvent +public record DomainEvent(Guid Id, DateTime CreationTime) : IDomainEvent { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; [JsonIgnore] - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } public DomainEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } - public DomainEvent(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs index a91c481e4..e72db94d6 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs @@ -1,22 +1,22 @@ namespace MASA.Contrib.DDD.Domain.Events; -public abstract record DomainQuery : IDomainQuery +public abstract record DomainQuery(Guid Id, DateTime CreationTime) : IDomainQuery where TResult : notnull { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; [JsonIgnore] - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork + { + get => null; + set => throw new NotSupportedException(nameof(UnitOfWork)); + } public abstract TResult Result { get; set; } public DomainQuery() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public DomainQuery(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs index a0f916a10..3c027cb0f 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs @@ -1,12 +1,9 @@ namespace MASA.Contrib.DDD.Domain.Events; -public abstract record IntegrationDomainEvent : DomainEvent, IIntegrationDomainEvent +public abstract record IntegrationDomainEvent(Guid Id, DateTime CreationTime) : DomainEvent(Id, CreationTime), IIntegrationDomainEvent { + [JsonIgnore] public abstract string Topic { get; set; } public IntegrationDomainEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public IntegrationDomainEvent(Guid id, DateTime creationTime) : base(id, creationTime) - { - } } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs b/src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs new file mode 100644 index 000000000..74c3f9690 --- /dev/null +++ b/src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs @@ -0,0 +1,21 @@ +๏ปฟnamespace MASA.Contrib.DDD.Domain.Internal; + +internal class InvokeBuilder +{ + internal delegate Task TaskInvokeDelegate(object target, params object[] parameters); + + internal static TaskInvokeDelegate Build(Type targetType, string methodName, Type parameterType) + { + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(0)); + var valueCast = Expression.Convert(valueObj, parameterType); + var instanceCast = Expression.Convert(targetParameter, targetType); + var methodCall = Expression.Call(instanceCast, methodName, new[] { parameterType }, valueCast); + + var castMethodCall = Expression.Convert(methodCall, typeof(Task)); + var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); + return lambda.Compile(); + } +} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj b/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj index e7e66739f..be122bd18 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj +++ b/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj @@ -7,9 +7,12 @@ - - - + + + + + + diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs b/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs index 8fdef4264..742ffa1bc 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs @@ -22,17 +22,17 @@ public Assembly[] Assemblies } private bool IsAggregateRootEntity(Type type) - => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && type != typeof(Entity) && typeof(IAggregateRoot).IsAssignableFrom(type); + => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && typeof(IAggregateRoot).IsAssignableFrom(type); private IEnumerable Types { get; set; } - public List AllEventTypes { get; private set; } + private IEnumerable GetTypes(Type type) => Types.Where(t => t.IsClass && type.IsAssignableFrom(t)); - public List AllDomainServiceTypes { get; private set; } + internal List AllEventTypes { get; private set; } - public List AllAggregateRootTypes { get; private set; } + internal List AllDomainServiceTypes { get; private set; } - private IEnumerable GetTypes(Type type) => Types.Where(t => type.IsAssignableFrom(t) && t.IsClass); + internal List AllAggregateRootTypes { get; private set; } public IServiceCollection Services { get; } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/README.md b/src/DDD/MASA.Contrib.DDD.Domain/README.md index dc44c3e61..842995a1e 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/README.md +++ b/src/DDD/MASA.Contrib.DDD.Domain/README.md @@ -1,3 +1,5 @@ +[ไธญ](README.zh-CN.md) | EN + ### DomainEventBus Example๏ผš @@ -10,7 +12,7 @@ Install-Package MASA.Contrib.Dispatcher.Events Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF -Install-Package MASA.Contrib.Data.Uow.EF +Install-Package MASA.Contrib.Data.UoW.EF ``` 1. Add DomainEventBus @@ -20,9 +22,9 @@ builder.Services .AddDomainEventBus(options => { options.UseEventBus()//Use in-process events - .UseUow(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) + .UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) .UseDaprEventBus()///Use cross-process events - .UseEventLog() + .UseEventLog() .UseRepository();//Use the EF version of Repository to achieve }) ``` @@ -36,7 +38,7 @@ public class RegisterUserDomainCommand : DomainCommand public string Password { get; set; } = default!; - public string Mobile { get; set; } = default!; + public string PhoneNumber { get; set; } = default!; } ``` > DomainQuery refers to Query in CQRS @@ -46,8 +48,8 @@ public class RegisterUserDomainCommand : DomainCommand ```C# public class UserHandler { - [EventHandler] - public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) + [EventHandler] + public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) { //TODO Registered user business } diff --git a/src/DDD/MASA.Contrib.DDD.Domain/README.zh-cn.md b/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md similarity index 89% rename from src/DDD/MASA.Contrib.DDD.Domain/README.zh-cn.md rename to src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md index bcad3c833..a6402cc4d 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/README.zh-cn.md +++ b/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md @@ -1,3 +1,5 @@ +ไธญ | [EN](README.md) + ### DomainEventBus ็”จไพ‹๏ผš @@ -10,7 +12,7 @@ Install-Package MASA.Contrib.Dispatcher.Events Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF -Install-Package MASA.Contrib.Data.Uow.EF +Install-Package MASA.Contrib.Data.UoW.EF ``` 1. ๆทปๅŠ DomainEventBus @@ -20,9 +22,9 @@ builder.Services .AddDomainEventBus(options => { options.UseEventBus()//ไฝฟ็”จ่ฟ›็จ‹ๅ†…ไบ‹ไปถ - .UseUow(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) + .UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) .UseDaprEventBus()///ไฝฟ็”จ่ทจ่ฟ›็จ‹ไบ‹ไปถ - .UseEventLog() + .UseEventLog() .UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ }) ``` @@ -36,7 +38,7 @@ public class RegisterUserDomainCommand : DomainCommand public string Password { get; set; } = default!; - public string Mobile { get; set; } = default!; + public string PhoneNumber { get; set; } = default!; } ``` > DomainQueryๅ‚่€ƒCQRSไธญ็š„Query @@ -46,8 +48,8 @@ public class RegisterUserDomainCommand : DomainCommand ```C# public class UserHandler { - [EventHandler] - public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) + [EventHandler] + public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) { //TODO ๆณจๅ†Œ็”จๆˆทไธšๅŠก } @@ -114,4 +116,4 @@ public async Task RegisterUserSucceededHandlerAsync(RegisterUserSucceededIntegra { //todo } -``` \ No newline at end of file +``` diff --git a/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs index 7211a238c..28c03d33e 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs @@ -1,5 +1,3 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; - namespace MASA.Contrib.DDD.Domain; public static class ServiceCollectionExtensions @@ -8,7 +6,9 @@ public static IServiceCollection AddDomainEventBus( this IServiceCollection services, Action? options = null) { - if (services.Any(service => service.ImplementationType == typeof(DomainEventBusProvider))) return services; + if (services.Any(service => service.ImplementationType == typeof(DomainEventBusProvider))) + return services; + services.AddSingleton(); var dispatcherOptions = new DispatcherOptions(services); @@ -17,7 +17,7 @@ public static IServiceCollection AddDomainEventBus( { dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); } - services.TryAddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); + services.AddSingleton(typeof(IOptions), serviceProvider => Options.Create(dispatcherOptions)); if (services.All(service => service.ServiceType != typeof(IEventBus))) { @@ -26,7 +26,7 @@ public static IServiceCollection AddDomainEventBus( if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) { - throw new Exception("Please add Uow first."); + throw new Exception("Please add UoW first."); } if (services.All(service => service.ServiceType != typeof(IIntegrationEventBus))) diff --git a/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs b/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs index 021c36b81..51c99d1c7 100644 --- a/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs +++ b/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs @@ -1,12 +1,15 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.DDD.Domain.Entities; global using MASA.BuildingBlocks.DDD.Domain.Events; global using MASA.BuildingBlocks.DDD.Domain.Repositories; global using MASA.BuildingBlocks.DDD.Domain.Services; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; +global using MASA.Contrib.DDD.Domain.Internal; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Options; global using System.Collections.Concurrent; +global using System.Linq.Expressions; global using System.Reflection; global using System.Text.Json.Serialization; diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj b/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj index c29fc9254..4219b95ea 100644 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj +++ b/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj @@ -7,10 +7,13 @@ - - - - - + + + + + + + + diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/README.md b/src/Data/MASA.Contrib.Data.Contracts.EF/README.md index 76ba4c345..04282f6fe 100644 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/README.md +++ b/src/Data/MASA.Contrib.Data.Contracts.EF/README.md @@ -1,20 +1,31 @@ +[ไธญ](README.zh-CN.md) | EN + ## Contracts.EF Example๏ผš ```C# +Install-Package MASA.Contrib.Data.UoW.EF Install-Package MASA.Contrib.Data.Contracts.EF ``` ```C# -builder.Services - .AddUoW(dbOptions => +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => { dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); dbOptions.UseSoftDelete(builder.Services); - }) + }); +}); ``` > When the entity inherits ISoftware and is deleted, change the delete state to the modified state, and cooperate with the custom Remove operation to achieve soft deletion > Do not query the data marked as soft deleted when querying -> When combined with EventBus, the transaction is opened after the first CUD, and the transaction rollback is supported when the entire Handler is abnormal. + +> Frequently Asked Questions: + +- Problem 1: After using UseSoftDelete, there is a problem that the submission cannot be saved + + After using Uow, the transaction will be enabled by default after Addใ€ Modifiedใ€ and Deleted + and the transaction can be saved normally after the transaction is submitted + If the EventBus is used, the transaction will be automatically submitted \ No newline at end of file diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-cn.md b/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md similarity index 51% rename from src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-cn.md rename to src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md index be3fb7afc..5de5f31ae 100644 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-cn.md +++ b/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md @@ -1,20 +1,31 @@ +ไธญ | [EN](README.md) + ## Contracts.EF ็”จไพ‹๏ผš ```C# +Install-Package MASA.Contrib.Data.UoW.EF Install-Package MASA.Contrib.Data.Contracts.EF ``` ```C# -builder.Services - .AddUoW(dbOptions => +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => { dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); dbOptions.UseSoftDelete(builder.Services);//ๅฏๅŠจ่ฝฏๅˆ ้™ค - }) + }); +}); ``` > ๅฝ“ๅฎžไฝ“็ปงๆ‰ฟISoftware๏ผŒไธ”่ขซๅˆ ้™คๆ—ถ๏ผŒๅฐ†ๅˆ ้™ค็Šถๆ€ๆ”นไธบไฟฎๆ”น็Šถๆ€๏ผŒๅนถ้…ๅˆ่‡ชๅฎšไน‰Removeๆ“ไฝœ๏ผŒๅฎž็Žฐ่ฝฏๅˆ ้™ค > ๆ”ฏๆŒๆŸฅ่ฏข็š„ๆ—ถๅ€™ไธๆŸฅ่ฏข่ขซๆ ‡่ฎฐ่ฝฏๅˆ ้™ค็š„ๆ•ฐๆฎ -> ไธŽEventBus็ป“ๅˆไฝฟ็”จๆ—ถ๏ผŒๅšๅˆฐไบ†็ฌฌไธ€ๆฌกCUDๅŽๅผ€ๅฏไบ‹ๅŠก๏ผŒๅฝ“ๆ•ดไธชHandlerๅ‡บ็Žฐๅผ‚ๅธธๅŽๆ”ฏๆŒไบ‹ๅŠกๅ›žๆปš + +> ๅธธ่ง้—ฎ้ข˜๏ผš + +- ้—ฎ้ข˜1๏ผšไฝฟ็”จUseSoftDeleteๅŽๅ‡บ็Žฐๆไบคไฟๅญ˜ไธไธŠ็š„้—ฎ้ข˜ + + ไฝฟ็”จUowๅŽ๏ผŒ้ป˜่ฎคๅœจ่ฟ›่กŒAddใ€Modifiedใ€DeletedๅŽไผšๅฏ็”จไบ‹ๅŠก + ้œ€่ฆๆไบคไบ‹ๅŠกไน‹ๅŽๆ‰่ƒฝๆญฃๅธธไฟๅญ˜ + ๅฆ‚ๆžœไฝฟ็”จEventBusๅˆ™ไผš่‡ชๅŠจๆไบคไบ‹ๅŠก \ No newline at end of file diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs index 77488ea6e..d15233ea7 100644 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs +++ b/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs @@ -12,7 +12,6 @@ public static MasaDbContextOptionsBuilder UseSoftDelete( throw new Exception("Please add UoW first."); optionsBuilder.UseQueryFilterProvider() - .UseSaveChangesFilter() .UseSaveChangesFilter(); return optionsBuilder; diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs index b739b1f94..da0023f04 100644 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs +++ b/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs @@ -4,11 +4,12 @@ public class SoftDeleteSaveChangesFilter : ISaveChangesFilter { public void OnExecuting(ChangeTracker changeTracker) { - foreach (var entity in changeTracker.Entries().Where(e => e.State == EntityState.Deleted)) + changeTracker.DetectChanges(); + foreach (var entity in changeTracker.Entries().Where(e => e.State == Microsoft.EntityFrameworkCore.EntityState.Deleted)) { if (entity.Entity is ISoftDelete) { - entity.State = EntityState.Modified; + entity.State = Microsoft.EntityFrameworkCore.EntityState.Modified; entity.CurrentValues[nameof(ISoftDelete.IsDeleted)] = true; } } diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/TransactionSaveChangesFilter.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/TransactionSaveChangesFilter.cs deleted file mode 100644 index a6c47eb16..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/TransactionSaveChangesFilter.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MASA.Contrib.Data.Contracts.EF.SoftDelete -{ - public class TransactionSaveChangesFilter : ISaveChangesFilter - { - private readonly IUnitOfWork _unitOfWork; - - public TransactionSaveChangesFilter(IUnitOfWork unitOfWork) => _unitOfWork = unitOfWork; - - public void OnExecuting(ChangeTracker changeTracker) - { - changeTracker.DetectChanges(); - if (changeTracker.Entries().Any(e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted) && !_unitOfWork.TransactionHasBegun) - { - var transaction = _unitOfWork.Transaction; // Open the transaction - } - } - } -} diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs index c160c452a..d4c3c90c7 100644 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs +++ b/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs @@ -1,5 +1,5 @@ global using MASA.BuildingBlocks.Data.Contracts; -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.Contrib.Data.Contracts.EF.SoftDelete; global using MASA.Utils.Data.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore; diff --git a/src/Data/MASA.Contrib.Data.Uow.EF/DispatcherOptionsExtensions.cs b/src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs similarity index 50% rename from src/Data/MASA.Contrib.Data.Uow.EF/DispatcherOptionsExtensions.cs rename to src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs index c7cb8535e..b549686ef 100644 --- a/src/Data/MASA.Contrib.Data.Uow.EF/DispatcherOptionsExtensions.cs +++ b/src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -1,33 +1,41 @@ -namespace MASA.Contrib.Data.Uow.EF; +namespace MASA.Contrib.Data.UoW.EF; public static class DispatcherOptionsExtensions { public static IDispatcherOptions UseUoW( this IDispatcherOptions options, - Action? optionsBuilder = null) + Action? optionsBuilder = null, + bool disableRollbackOnFailure = false, + bool useTransaction = true) where TDbContext : MasaDbContext { if (options.Services == null) - { throw new ArgumentNullException(nameof(options.Services)); - } - if (options.Services.Any(service => service.ImplementationType == typeof(UowProvider))) return options; - options.Services.AddSingleton(); + if (options.Services.Any(service => service.ImplementationType == typeof(UoWProvider))) + return options; - options.Services.AddLogging(); - options.Services.AddScoped>(); - if (options.Services.All(service => service.ServiceType != typeof(MasaDbContextOptions))) + options.Services.AddSingleton(); + + options.Services.AddScoped(serviceProvider => { + var dbContext = serviceProvider.GetRequiredService(); + var logger = serviceProvider.GetService>>(); + return new UnitOfWork(dbContext, logger) + { + DisableRollbackOnFailure = disableRollbackOnFailure, + UseTransaction = useTransaction + }; + }); + if (options.Services.All(service => service.ServiceType != typeof(MasaDbContextOptions))) options.Services.AddMasaDbContext(optionsBuilder); - } options.Services.AddScoped(); return options; } - private class UowProvider + private class UoWProvider { } diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj b/src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj new file mode 100644 index 000000000..afd4da368 --- /dev/null +++ b/src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj @@ -0,0 +1,20 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/README.md b/src/Data/MASA.Contrib.Data.UoW.EF/README.md new file mode 100644 index 000000000..efd89e947 --- /dev/null +++ b/src/Data/MASA.Contrib.Data.UoW.EF/README.md @@ -0,0 +1,20 @@ +[ไธญ](README.zh-CN.md) | EN + +## Contracts.EF + +Example๏ผš + +```C# +Install-Package MASA.Contrib.Data.UoW.EF +Install-Package MASA.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + }); +}); +``` + diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md b/src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md new file mode 100644 index 000000000..316b94e90 --- /dev/null +++ b/src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md @@ -0,0 +1,20 @@ +ไธญ | [EN](README.md) + +## Contracts.EF + +็”จไพ‹๏ผš + +```C# +Install-Package MASA.Contrib.Data.UoW.EF +Install-Package MASA.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + }); +}); +``` + diff --git a/src/Data/MASA.Contrib.Data.Uow.EF/Transaction.cs b/src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs similarity index 52% rename from src/Data/MASA.Contrib.Data.Uow.EF/Transaction.cs rename to src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs index c4a02136c..f7fdf6f2b 100644 --- a/src/Data/MASA.Contrib.Data.Uow.EF/Transaction.cs +++ b/src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs @@ -1,11 +1,9 @@ -using System.Text.Json.Serialization; - -namespace MASA.Contrib.Data.Uow.EF; +namespace MASA.Contrib.Data.UoW.EF; public class Transaction : ITransaction { public Transaction(IUnitOfWork unitOfWork) => UnitOfWork = unitOfWork; [JsonIgnore] - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } } diff --git a/src/Data/MASA.Contrib.Data.Uow.EF/UnitOfWork.cs b/src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs similarity index 53% rename from src/Data/MASA.Contrib.Data.Uow.EF/UnitOfWork.cs rename to src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs index bb619503f..eaacfefac 100644 --- a/src/Data/MASA.Contrib.Data.Uow.EF/UnitOfWork.cs +++ b/src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs @@ -1,16 +1,18 @@ -namespace MASA.Contrib.Data.Uow.EF; +namespace MASA.Contrib.Data.UoW.EF; -public class UnitOfWork : IUnitOfWork +public class UnitOfWork : IAsyncDisposable, IUnitOfWork where TDbContext : MasaDbContext { public DbTransaction Transaction { get { + if (!UseTransaction) + throw new NotSupportedException("Doesn't support transaction opening"); + if (TransactionHasBegun) - { return _context.Database.CurrentTransaction!.GetDbTransaction(); - } + return _context.Database.BeginTransaction().GetDbTransaction(); } } @@ -19,11 +21,17 @@ public DbTransaction Transaction public bool DisableRollbackOnFailure { get; set; } + public EntityState EntityState { get; set; } + + public CommitState CommitState { get; set; } + + public bool UseTransaction { get; set; } = true; + private readonly DbContext _context; - private readonly ILogger> _logger; + private readonly ILogger>? _logger; - public UnitOfWork(TDbContext dbContext, ILogger> logger) + public UnitOfWork(TDbContext dbContext, ILogger>? logger = null) { _context = dbContext; _logger = logger; @@ -32,42 +40,27 @@ public UnitOfWork(TDbContext dbContext, ILogger> logger) public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { await _context.SaveChangesAsync(cancellationToken); + EntityState = EntityState.Unchanged; } public async Task CommitAsync(CancellationToken cancellationToken = default) { - if (!TransactionHasBegun) - { - await SaveChangesAsync(); - return; - } + if (!UseTransaction || !TransactionHasBegun) + throw new NotSupportedException("Transaction not opened"); - try - { - await SaveChangesAsync(); - await _context.Database.CommitTransactionAsync(cancellationToken); - } - catch (Exception ex) - { - if (DisableRollbackOnFailure) - { - _logger.LogError(ex, "Failed to commit transaction"); - throw; - } - await _context.Database.RollbackTransactionAsync(cancellationToken); - _logger.LogError(ex, "Failed to commit transaction, rolled back"); - } + await _context.Database.CommitTransactionAsync(cancellationToken); + CommitState = CommitState.Commited; } public async Task RollbackAsync(CancellationToken cancellationToken = default) { - if (!TransactionHasBegun) - throw new NotSupportedException("Transactions are not started and rollback is not supported"); + if (!UseTransaction || !TransactionHasBegun) + throw new NotSupportedException("Transactions are not opened and rollback is not supported"); + await _context.Database.RollbackTransactionAsync(cancellationToken); } - public ValueTask DisposeAsync() - { - return ValueTask.CompletedTask; - } + public ValueTask DisposeAsync() => _context.DisposeAsync(); + + public void Dispose() => _context.Dispose(); } diff --git a/src/Data/MASA.Contrib.Data.Uow.EF/_Imports.cs b/src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs similarity index 67% rename from src/Data/MASA.Contrib.Data.Uow.EF/_Imports.cs rename to src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs index 005db3fab..cfd1ca3f9 100644 --- a/src/Data/MASA.Contrib.Data.Uow.EF/_Imports.cs +++ b/src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs @@ -1,4 +1,4 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.Utils.Data.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore; @@ -6,3 +6,6 @@ global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; global using System.Data.Common; +global using System.Text.Json.Serialization; +global using EntityState = MASA.BuildingBlocks.Data.UoW.EntityState; + diff --git a/src/Data/MASA.Contrib.Data.Uow.EF/MASA.Contrib.Data.Uow.EF.csproj b/src/Data/MASA.Contrib.Data.Uow.EF/MASA.Contrib.Data.Uow.EF.csproj deleted file mode 100644 index 9a41bc05d..000000000 --- a/src/Data/MASA.Contrib.Data.Uow.EF/MASA.Contrib.Data.Uow.EF.csproj +++ /dev/null @@ -1,17 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs index bcab2ea47..9431ab1d1 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs @@ -1,16 +1,12 @@ namespace MASA.Contrib.Dispatcher.Events; -public record Event : IEvent +public record Event(Guid Id, DateTime CreationTime) : IEvent { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; public Event() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public Event(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs index 4b8c5ddd2..1f7cd0cd8 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs @@ -10,6 +10,8 @@ public class EventBus : IEventBus private IUnitOfWork? _unitOfWork; + private readonly string LoadEventHelpLink = "https://github.com/masastack/MASA.Contrib/tree/develop/docs/LoadEvent.md"; + public EventBus(IServiceProvider serviceProvider, IOptions options) { _serviceProvider = serviceProvider; @@ -19,14 +21,21 @@ public EventBus(IServiceProvider serviceProvider, IOptions op public async Task PublishAsync(TEvent @event) where TEvent : IEvent { + var eventType = typeof(TEvent); if (@event is null) { - throw new ArgumentNullException(typeof(TEvent).Name); + throw new ArgumentNullException(eventType.Name); } var middlewares = _serviceProvider.GetRequiredService>>(); - if (@event is ITransaction transactionEvent) + if (!_options.UnitOfWorkRelation.ContainsKey(eventType)) { + throw new NotSupportedException($"Getting \"{eventType.Name}\" relationship chain failed, see {LoadEventHelpLink} for details. "); + } + + if (_options.UnitOfWorkRelation[eventType]) + { + ITransaction transactionEvent = (ITransaction) @event; var unitOfWork = _serviceProvider.GetService(); if (unitOfWork != null) { @@ -42,12 +51,17 @@ public async Task PublishAsync(TEvent @event) where TEvent : IEvent } } - EventHandlerDelegate publishEvent = async () => - { - await _dispatcher.PublishEventAsync(_serviceProvider, @event); - }; + EventHandlerDelegate publishEvent = async () => { await _dispatcher.PublishEventAsync(_serviceProvider, @event); }; await middlewares.Reverse().Aggregate(publishEvent, (next, middleware) => () => middleware.HandleAsync(@event, next))(); } - public IEnumerable GetAllEventTypes() => _options.GetAllEventTypes(); + public IEnumerable GetAllEventTypes() => _options.AllEventTypes; + + public async Task CommitAsync(CancellationToken cancellationToken = default) + { + if (_unitOfWork is null) + throw new ArgumentNullException("You need to UseUoW when adding services"); + + await _unitOfWork.CommitAsync(cancellationToken); + } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs index 9c79ebf3d..645c988a3 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs @@ -8,9 +8,9 @@ internal class DispatchRelationNetwork public Dictionary> CancelRelationNetwork { get; set; } = new(); - private readonly ILogger _logger; + private readonly ILogger? _logger; - public DispatchRelationNetwork(ILogger logger) => _logger = logger; + public DispatchRelationNetwork(ILogger? logger) => _logger = logger; public void Add(Type keyEventType, EventHandlerAttribute handler) { @@ -82,7 +82,7 @@ private void CheckConstraints() { foreach (var cancelRelation in CancelRelationNetwork) { - if (!HandlerRelationNetwork.Any(relation => relation.Key == cancelRelation.Key)) + if (HandlerRelationNetwork.All(relation => relation.Key != cancelRelation.Key)) { throw new NotSupportedException($"{cancelRelation.Key.Name} is only have a cancel handler, it must have an event handler."); } @@ -92,7 +92,7 @@ private void CheckConstraints() if (maxHandlerOrder == null || maxHandlerOrder.IsHandlerMissing(maxCancelOrder)) { var methodName = cancelRelation.Value.Select(x => x.ActionMethodInfo.Name).LastOrDefault(); - _logger.LogWarning($"The {methodName} method is meaningless, because its Order attribute is too large, and no handler corresponding to the Order can be triggered. It is suggested to lower the Order attribute of {methodName} or add a matching handler - {cancelRelation.Value.Select(x => x.InstanceType.FullName).LastOrDefault()}"); + _logger?.LogWarning($"The {methodName} method is meaningless, because its Order attribute is too large, and no handler corresponding to the Order can be triggered. It is suggested to lower the Order attribute of {methodName} or add a matching handler - {cancelRelation.Value.Select(x => x.InstanceType.FullName).LastOrDefault()}"); } } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs index 3e789509b..412d5ab03 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs @@ -2,11 +2,11 @@ namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; internal class Dispatcher : DispatcherBase { - public Dispatcher(IServiceCollection services, bool forceInit = false) : base(services, forceInit) { } + public Dispatcher(IServiceCollection services, Assembly[] assemblies, bool forceInit = false) : base(services, assemblies, forceInit) { } - public Dispatcher Build(ServiceLifetime lifetime, params Assembly[] assemblies) + public Dispatcher Build(ServiceLifetime lifetime) { - foreach (var assembly in assemblies) + foreach (var assembly in _assemblies) { AddRelationNetwork(assembly); } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs index 6dd13ab60..7074e5be1 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs @@ -2,39 +2,40 @@ namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; internal class DispatcherBase { - protected static DispatchRelationNetwork _sharingRelationNetwork; + protected static DispatchRelationNetwork? _sharingRelationNetwork; protected readonly IServiceCollection _services; - private readonly ILogger _logger; + protected readonly Assembly[] _assemblies; - public DispatcherBase(IServiceCollection services, bool forceInit) + private readonly ILogger? _logger; + + public DispatcherBase(IServiceCollection services, Assembly[] assemblies, bool forceInit) { _services = services; + _assemblies = assemblies; var serviceProvider = services.BuildServiceProvider(); if (_sharingRelationNetwork == null || forceInit) { - _sharingRelationNetwork = new DispatchRelationNetwork(serviceProvider.GetRequiredService>()); + _sharingRelationNetwork = new DispatchRelationNetwork(serviceProvider.GetService>()); } - _logger = serviceProvider.GetRequiredService>(); + _logger = serviceProvider.GetService>(); } public async Task PublishEventAsync(IServiceProvider serviceProvider, TEvent @event) where TEvent : IEvent { var eventType = typeof(TEvent); - if (!_sharingRelationNetwork.RelationNetwork.TryGetValue(eventType, out List? dispatchRelations) || dispatchRelations == null) + if (!_sharingRelationNetwork!.RelationNetwork.TryGetValue(eventType, out List? dispatchRelations)) { - if(@event is IIntegrationEvent) + if (@event is IIntegrationEvent) { - _logger.LogError($"Dispatcher: The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); + _logger?.LogError($"Dispatcher: The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); throw new ArgumentNullException($"The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); } - else - { - _logger.LogError($"Dispatcher: The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); - throw new ArgumentNullException($"The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); - } + + _logger?.LogError($"Dispatcher: The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); + throw new ArgumentNullException($"The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); } await ExecuteEventHandlerAsync(serviceProvider, dispatchRelations, @event); } @@ -57,7 +58,7 @@ private async Task ExecuteEventHandlerAsync(IServiceProvider serviceProv await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => { - _logger.LogDebug("----- Publishing event {@Event}: message id: {messageId} -----", @event, @event.Id); + _logger?.LogDebug("----- Publishing event {@Event}: message id: {messageId} -----", @event, @event.Id); await dispatchHandler.ExcuteAction(serviceProvider, @event); }, async (@event, ex, failureLevels) => { @@ -75,14 +76,14 @@ await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => } else { - _logger.LogWarning("----- Publishing event {@Event} error rollback is ignored: message id: {messageId} -----", @event, @event.Id); + _logger?.LogWarning("----- Publishing event {@Event} error rollback is ignored: message id: {messageId} -----", @event, @event.Id); } }); } } private async Task ExecuteEventCanceledHandlerAsync(IServiceProvider serviceProvider, - ILogger logger, + ILogger? logger, IExecutionStrategy executionStrategy, IEnumerable cancelHandlers, TEvent @event) @@ -92,9 +93,9 @@ private async Task ExecuteEventCanceledHandlerAsync(IServiceProvider ser foreach (var cancelHandler in cancelHandlers) { strategyOptions.SetStrategy(cancelHandler); - await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => + await executionStrategy.ExecuteAsync(strategyOptions, @event, async @event => { - logger.LogDebug("----- Publishing event {@Event} rollback start: message id: {messageId} -----", @event, @event.Id); + logger?.LogDebug("----- Publishing event {@Event} rollback start: message id: {messageId} -----", @event, @event.Id); await cancelHandler.ExcuteAction(serviceProvider, @event); }, (@event, ex, failureLevels) => { @@ -102,7 +103,7 @@ await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => { throw ex; } - logger.LogWarning("----- Publishing event {@Event} rollback error ignored: message id: {messageId} -----", @event, @event.Id); + logger?.LogWarning("----- Publishing event {@Event} rollback error ignored: message id: {messageId} -----", @event, @event.Id); return Task.CompletedTask; }); } @@ -110,16 +111,16 @@ await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => protected void AddRelationNetwork(Type parameterType, EventHandlerAttribute handler) { - _sharingRelationNetwork.Add(parameterType, handler); + _sharingRelationNetwork!.Add(parameterType, handler); } - protected IEnumerable GetAddServiceTypeList() => _sharingRelationNetwork.HandlerRelationNetwork + protected IEnumerable GetAddServiceTypeList() => _sharingRelationNetwork!.HandlerRelationNetwork .Concat(_sharingRelationNetwork.CancelRelationNetwork) .SelectMany(relative => relative.Value) .Where(dispatchHandler => dispatchHandler.InvokeDelegate != null) .Select(dispatchHandler => dispatchHandler.InstanceType).Distinct(); - protected void Build() => _sharingRelationNetwork.Build(); + protected void Build() => _sharingRelationNetwork!.Build(); protected bool IsSagaMode(Type handlerType, MethodInfo method) => typeof(IEventHandler<>).IsGenericInterfaceAssignableFrom(handlerType) && method.Name.Equals(nameof(IEventHandler.HandleAsync)) || diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs index af24ed1e8..c68170662 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs @@ -2,18 +2,18 @@ namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; internal class SagaDispatcher : DispatcherBase { - public SagaDispatcher(IServiceCollection services, bool forceInit = false) : base(services, forceInit) { } + public SagaDispatcher(IServiceCollection services, Assembly[] assemblies, bool forceInit = false) : base(services, assemblies, forceInit) { } - public SagaDispatcher Build(ServiceLifetime lifetime, params Assembly[] assemblies) + public SagaDispatcher Build(ServiceLifetime lifetime) { - AddSagaDispatchRelation(_services, typeof(IEventHandler<>), lifetime, assemblies); - AddSagaDispatchRelation(_services, typeof(ISagaEventHandler<>), lifetime, assemblies); + AddSagaDispatchRelation(_services, typeof(IEventHandler<>), lifetime); + AddSagaDispatchRelation(_services, typeof(ISagaEventHandler<>), lifetime); return this; } - private IServiceCollection AddSagaDispatchRelation(IServiceCollection services, Type eventBusHandlerType, ServiceLifetime lifetime, params Assembly[] assemblies) + private IServiceCollection AddSagaDispatchRelation(IServiceCollection services, Type eventBusHandlerType, ServiceLifetime lifetime) { - foreach (var item in GetAddSagaServices(eventBusHandlerType, assemblies)) + foreach (var item in GetAddSagaServices(eventBusHandlerType)) { services.Add(item.ServiceType, item.ImplementationType, lifetime); AddSagaRelationNetwork(item.ImplementationType); @@ -80,10 +80,10 @@ private List GetSagaHandlers(Type eventBusHandlerType) return eventHandlers; } - private List<(Type ServiceType, Type ImplementationType)> GetAddSagaServices(Type eventBusHandlerType, params Assembly[] assemblies) + private List<(Type ServiceType, Type ImplementationType)> GetAddSagaServices(Type eventBusHandlerType) { List<(Type ServiceType, Type ImplementationType)> list = new(); - var serviceTypeAndImplementationInfo = GetSagaServiceTypeAndImplementations(eventBusHandlerType, assemblies); + var serviceTypeAndImplementationInfo = GetSagaServiceTypeAndImplementations(eventBusHandlerType); foreach (var serviceType in serviceTypeAndImplementationInfo.ServiceTypeList) { var implementationTypes = serviceTypeAndImplementationInfo.ImplementationType.Where(implementationType => serviceType.IsAssignableFrom(implementationType)).ToList(); @@ -97,11 +97,11 @@ private List GetSagaHandlers(Type eventBusHandlerType) return list; } - private (List ServiceTypeList, List ImplementationType) GetSagaServiceTypeAndImplementations(Type eventBusHandlerType, params Assembly[] assemblies) + private (List ServiceTypeList, List ImplementationType) GetSagaServiceTypeAndImplementations(Type eventBusHandlerType) { var concretions = new List(); var interfaces = new List(); - foreach (var type in assemblies.SelectMany(a => a.DefinedTypes).Where(t => !t.IsGeneric())) + foreach (var type in _assemblies.SelectMany(a => a.DefinedTypes).Where(t => !t.IsGeneric())) { if (type.IsConcrete()) { diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs index 91e1fd9de..a1abbcedf 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs @@ -3,11 +3,11 @@ namespace MASA.Contrib.Dispatcher.Events.Internal.Middleware; public class TransactionMiddleware : IMiddleware where TEvent : notnull, IEvent { - private readonly ILogger> _logger; + private readonly IUnitOfWork? _unitOfWork; - public TransactionMiddleware(ILogger> logger) + public TransactionMiddleware(IUnitOfWork? unitOfWork = null) { - _logger = logger; + _unitOfWork = unitOfWork; } public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) @@ -16,33 +16,34 @@ public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) { await next(); - if (@event is ITransaction transactionEvent) + if (_unitOfWork is { EntityState: EntityState.Changed }) { - if (transactionEvent.UnitOfWork != null) - { - if (transactionEvent.UnitOfWork.TransactionHasBegun) - { - await transactionEvent.UnitOfWork.CommitAsync(); - } - else - { - await transactionEvent.UnitOfWork.SaveChangesAsync(); - } - } + await _unitOfWork.SaveChangesAsync(); } - } - catch (Exception ex) - { - _logger.LogError(ex, nameof(TransactionMiddleware)); - - if (@event is ITransaction transactionEvent && transactionEvent.UnitOfWork != null && transactionEvent.UnitOfWork.TransactionHasBegun && !transactionEvent.UnitOfWork.DisableRollbackOnFailure) + if (IsUseTransaction(@event, out ITransaction? transaction)) { - await transactionEvent.UnitOfWork.RollbackAsync(); + await transaction!.UnitOfWork!.CommitAsync(); } - else + } + catch (Exception) + { + if (IsUseTransaction(@event, out ITransaction? transaction) && !transaction!.UnitOfWork!.DisableRollbackOnFailure) { - throw; + await transaction.UnitOfWork!.RollbackAsync(); } + throw; } } + + private bool IsUseTransaction(TEvent @event, out ITransaction? transaction) + { + if (@event is ITransaction { UnitOfWork: { UseTransaction: true, TransactionHasBegun: true, CommitState: CommitState.UnCommited } } transactionEvent) + { + transaction = transactionEvent; + return true; + } + + transaction = null; + return false; + } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj index a80128258..1e61ad2c1 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj @@ -7,13 +7,16 @@ - - - - - - - + + + + + + + + + + diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs index 6e7cd6a06..3db90c640 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs @@ -2,7 +2,7 @@ namespace MASA.Contrib.Dispatcher.Events.Options; public class DispatcherOptions : IDispatcherOptions { - private Assembly[] _assemblies = new Assembly[0]; + private Assembly[] _assemblies = Array.Empty(); public Assembly[] Assemblies { @@ -14,20 +14,23 @@ public Assembly[] Assemblies { throw new ArgumentNullException(nameof(_assemblies)); } - Types = _assemblies.SelectMany(assembly => assembly.GetTypes()).ToList(); - _allEventTypes = GetTypes(typeof(IEvent)).ToList(); + AllEventTypes = _assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) + .ToList(); + + UnitOfWorkRelation = AllEventTypes.ToDictionary(type => type, IsSupportUnitOfWork); } } - private IEnumerable Types { get; set; } + private bool IsSupportUnitOfWork(Type eventType) + => typeof(ITransaction).IsAssignableFrom(eventType) && !typeof(IDomainQuery<>).IsGenericInterfaceAssignableFrom(eventType); - private IEnumerable GetTypes(Type type) => Types.Where(t => type.IsAssignableFrom(t) && t.IsClass); + internal Dictionary UnitOfWorkRelation { get; set; } = new(); - public IEnumerable GetAllEventTypes() => _allEventTypes; + public IEnumerable AllEventTypes { get; private set; } public IServiceCollection Services { get; } - private IEnumerable _allEventTypes; - public DispatcherOptions(IServiceCollection services) => Services = services; } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md index 2ca2d1622..0d6177dd5 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md @@ -1,3 +1,5 @@ +[ไธญ](README.zh-CN.md) | EN + ## EventBus Example๏ผš @@ -13,8 +15,8 @@ Install-Package MASA.Contrib.Dispatcher.Events ```c# var builder = WebApplication.CreateBuilder(args); var app = builder.Services - .AddEventBus() - //TODO + .AddEventBus() + //TODO ``` 2. Custom Event @@ -171,3 +173,22 @@ builder.Services 4. Support Transaction > Used in conjunction with Contracts.EF and UnitOfWork, when Event implements ITransaction, the transaction will be automatically opened after the first CUD is executed, and the transaction will be submitted after all Handlers are executed. When an exception occurs in the transaction, the transaction will be automatically rolled back. + +##### Summarize + +IEventBus is the core of the event bus. It can be used with CQRS, Uow, MASA.Contrib.DDD.Domain.Repository.EF to automatically execute SaveChange (enable UoW) and Commit (enable UoW without closing transaction) operations after sending Command, And support to roll back the transaction after an exception occurs + +> Question 1. Publishing events through eventBus, Handler error -> and handler throw exception + + > 1. Check custom events or inherited classes to make sure ITransaction is implemented + > 2. Confirm that UoW is used + > 3. Make sure the UseTransaction property of UnitOfWork is false + > 4. Make sure that the DisableRollbackOnFailure property of UnitOfWork is true + +> Question 2. Under what circumstances will SaveChange be automatically saved -> When auto call SaveChange? + + > Use UoW and MASA.Contrib.DDD.Domain.Repository.EF, and use the Add, Update, Delete operations provided by IRepository, publish events through EventBus, and automatically execute SaveChange after executing EventHandler + +> Question 3. If the SaveChange method of UoW is manually called in EventHandler to save, will the framework also save automatically? + + > If the SaveChange method of UoW is manually called in the EventHandler to save, and the Add, Update, and Delete operations provided by IRepository are not used afterward, the SaveChange operation will not be executed twice after the EventHandler execution ends, but if the UoW is manually called. After the SaveChange method is saved and continue to use the Add, Update, and Delete operations provided by IRepository, the framework will call the SaveChange operation again to ensure that the data is saved successfully. \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-cn.md b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md similarity index 68% rename from src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-cn.md rename to src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md index 7d857d060..271e987d9 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-cn.md +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md @@ -1,3 +1,5 @@ +ไธญ | [EN](README.md) + ## EventBus ็”จไพ‹๏ผš @@ -13,8 +15,8 @@ Install-Package MASA.Contrib.Dispatcher.Events ```c# var builder = WebApplication.CreateBuilder(args); var app = builder.Services - .AddEventBus() - //TODO + .AddEventBus() + //TODO ``` 2. ่‡ชๅฎšไน‰Event @@ -170,4 +172,23 @@ builder.Services 4. ๆ”ฏๆŒTransaction -> ้…ๅˆContracts.EFใ€UnitOfWorkไฝฟ็”จ๏ผŒๅฝ“Eventๅฎž็Žฐไบ†ITransaction๏ผŒไผšๅœจๆ‰ง่กŒ็ฌฌไธ€ๆฌกCUDๅŽ่‡ชๅŠจๅผ€ๅฏไบ‹ๅŠก๏ผŒไธ”ๅœจHandlerๅ…จ้ƒจๆ‰ง่กŒๅŽๆไบคไบ‹ๅŠก๏ผŒๅฝ“ไบ‹ๅŠกๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒไผš่‡ชๅŠจๅ›žๆปšไบ‹ๅŠก +> ้…ๅˆMASA.Contrib.DDD.Domain.Repository.EF.Repositoryใ€UnitOfWorkไฝฟ็”จ๏ผŒๅฝ“Eventๅฎž็Žฐไบ†ITransaction๏ผŒไผšๅœจๆ‰ง่กŒAddใ€Updateใ€Deleteๆ–นๆณ•ๆ—ถ่‡ชๅŠจๅผ€ๅฏไบ‹ๅŠก๏ผŒไธ”ๅœจHandlerๅ…จ้ƒจๆ‰ง่กŒๅŽๆไบคไบ‹ๅŠก๏ผŒๅฝ“ไบ‹ๅŠกๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒไผš่‡ชๅŠจๅ›žๆปšไบ‹ๅŠก + +##### ๆ€ป็ป“ + +IEventBusๆ˜ฏไบ‹ไปถๆ€ป็บฟ็š„ๆ ธๅฟƒ๏ผŒ้…ๅˆCQRSใ€Uowใ€MASA.Contrib.DDD.Domain.Repository.EFไฝฟ็”จ๏ผŒๅฏๅฎž็Žฐ่‡ชๅŠจๆ‰ง่กŒSaveChange๏ผˆๅฏ็”จUoW๏ผ‰ไธŽCommit๏ผˆๅฏ็”จUoWไธ”ๆ— ๅ…ณ้—ญไบ‹ๅŠก๏ผ‰ๆ“ไฝœ๏ผŒๅนถๆ”ฏๆŒๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒๅ›žๆปšไบ‹ๅŠก + +> ้—ฎ้ข˜1. ้€š่ฟ‡eventBusๅ‘ๅธƒไบ‹ไปถ๏ผŒHandlerๅ‡บ้”™๏ผŒไฝ†ๆ•ฐๆฎไพ็„ถไฟๅญ˜ๅˆฐๆ•ฐๆฎๅบ“ไธญ๏ผŒไบ‹ๅŠกๅนถๆœชๅ›žๆปš + + > 1. ๆฃ€ๆŸฅ่‡ชๅฎšไน‰ไบ‹ไปถๆˆ–็ปงๆ‰ฟ็ฑป๏ผŒ็กฎไฟๅทฒ็ปๅฎž็ŽฐITransaction + > 2. ็กฎ่ฎคๅทฒไฝฟ็”จUoW + > 3. ็กฎ่ฎคUnitOfWork็š„UseTransactionๅฑžๆ€งไธบfalse + > 4. ็กฎ่ฎคUnitOfWork็š„DisableRollbackOnFailureๅฑžๆ€งไธบtrue + +> ้—ฎ้ข˜2. ไป€ไนˆๆ—ถๅ€™่‡ชๅŠจ่ฐƒ็”จSaveChanges + + > ไฝฟ็”จUoWไธ”ไฝฟ็”จไบ†MASA.Contrib.DDD.Domain.Repository.EF๏ผŒๅนถไธ”ไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒ้€š่ฟ‡EventBusๅ‘ๅธƒไบ‹ไปถ๏ผŒๅœจๆ‰ง่กŒEventHandlerๅŽไผš่‡ชๅŠจๆ‰ง่กŒSaveChange + +> ้—ฎ้ข˜3. ๅฆ‚ๆžœๅœจEventHandlerไธญๆ‰‹ๅŠจ่ฐƒ็”จUoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜๏ผŒ้‚ฃๆก†ๆžถ่ฟ˜ไผš่‡ชๅŠจไฟๅญ˜ๅ—๏ผŸ + + > ๅฆ‚ๆžœๅœจEventHandlerไธญๆ‰‹ๅŠจ่ฐƒ็”จไบ†UoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜๏ผŒไธ”ไน‹ๅŽๅนถๆœชๅ†ไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒๅˆ™ๅœจEventHandlerๆ‰ง่กŒ็ป“ๆŸๅŽไธไผšไบŒๆฌกๆ‰ง่กŒSaveChangeๆ“ไฝœ๏ผŒไฝ†ๅฆ‚ๆžœๅœจๆ‰‹ๅŠจ่ฐƒ็”จUoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜ๅŽๅˆ็ปง็ปญไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒๅˆ™ๆก†ๆžถไผšๅ†ๆฌก่ฐƒ็”จSaveChangeๆ“ไฝœไปฅ็กฎไฟๆ•ฐๆฎไฟๅญ˜ๆˆๅŠŸ \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs index 24b55c4e3..a5f32a733 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs @@ -12,11 +12,9 @@ public static IServiceCollection AddEventBus( ServiceLifetime lifetime, Action? options = null) { - if (services.Any(service => service.ImplementationType == typeof (EventBusProvider))) return services; + if (services.Any(service => service.ImplementationType == typeof(EventBusProvider))) return services; services.AddSingleton(); - services.AddLogging(); - DispatcherOptions dispatcherOptions = new DispatcherOptions(services); options?.Invoke(dispatcherOptions); if (dispatcherOptions.Assemblies.Length == 0) @@ -25,8 +23,8 @@ public static IServiceCollection AddEventBus( } services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); - services.AddSingleton(new SagaDispatcher(services).Build(lifetime, dispatcherOptions.Assemblies)); - services.AddSingleton(new Internal.Dispatch.Dispatcher(services).Build(lifetime, dispatcherOptions.Assemblies)); + services.AddSingleton(new SagaDispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); + services.AddSingleton(new Internal.Dispatch.Dispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); services.TryAdd(typeof(IExecutionStrategy), typeof(ExecutionStrategy), ServiceLifetime.Singleton); services.AddTransient(typeof(IMiddleware<>), typeof(TransactionMiddleware<>)); services.AddScoped(typeof(IEventBus), typeof(EventBus)); @@ -36,21 +34,19 @@ public static IServiceCollection AddEventBus( public static IServiceCollection AddTestEventBus(this IServiceCollection services, ServiceLifetime lifetime, Action? options = null) { - if (services.Any(service => service.ImplementationType == typeof (EventBusProvider))) return services; + if (services.Any(service => service.ImplementationType == typeof(EventBusProvider))) return services; services.AddSingleton(); - services.AddLogging(); - DispatcherOptions dispatcherOptions = new DispatcherOptions(services); options?.Invoke(dispatcherOptions); if (dispatcherOptions.Assemblies.Length == 0) { dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); } - services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); - services.AddSingleton(new SagaDispatcher(services, true).Build(lifetime, dispatcherOptions.Assemblies)); - services.AddSingleton(new Internal.Dispatch.Dispatcher(services).Build(lifetime, dispatcherOptions.Assemblies)); + services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); + services.AddSingleton(new SagaDispatcher(services, dispatcherOptions.Assemblies, true).Build(lifetime)); + services.AddSingleton(new Internal.Dispatch.Dispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); services.TryAdd(typeof(IExecutionStrategy), typeof(ExecutionStrategy), ServiceLifetime.Singleton); services.AddTransient(typeof(IMiddleware<>), typeof(TransactionMiddleware<>)); services.AddScoped(typeof(IEventBus), typeof(EventBus)); diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs index 348c5b3fc..a467f31fe 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs @@ -2,9 +2,9 @@ namespace MASA.Contrib.Dispatcher.Events.Strategies; public class ExecutionStrategy : IExecutionStrategy { - private readonly ILogger _logger; + private readonly ILogger? _logger; - public ExecutionStrategy(ILogger logger) => _logger = logger; + public ExecutionStrategy(ILogger? logger = null) => _logger = logger; public async Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @event, Func func, Func cancel) where TEvent : IEvent @@ -18,7 +18,7 @@ public async Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @ { if (retryTimes > 0) { - _logger.LogWarning("----- Error Publishing event {@Event} start: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); + _logger?.LogWarning("----- Error Publishing event {@Event} start: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); } await func.Invoke(@event); return; @@ -27,11 +27,11 @@ public async Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @ { if (retryTimes > 0) { - _logger.LogWarning("----- Error Publishing event {@Event} finish: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); + _logger?.LogWarning("----- Error Publishing event {@Event} finish: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); } else { - _logger.LogWarning(ex, "----- Error Publishing event {@Event}: after {retries}th executions and we will stop retrying. message id: {messageId} -----", @event, strategyOptions.MaxRetryCount, @event.Id); + _logger?.LogWarning(ex, "----- Error Publishing event {@Event}: after {retries}th executions and we will stop retrying. message id: {messageId} -----", @event, strategyOptions.MaxRetryCount, @event.Id); } exception = ex; retryTimes++; diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs index bce7d4024..b3e839e01 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs @@ -1,4 +1,5 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; +global using MASA.BuildingBlocks.DDD.Domain.Events; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; global using MASA.Contrib.Dispatcher.Events.Enums; @@ -14,3 +15,4 @@ global using Microsoft.Extensions.Options; global using System.Linq.Expressions; global using System.Reflection; +global using System.Text.Json.Serialization; diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs new file mode 100644 index 000000000..b2eb56cff --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs @@ -0,0 +1,6 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public interface IProcessingServer +{ + Task ExecuteAsync(CancellationToken stoppingToken); +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs new file mode 100644 index 000000000..e33755dde --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs @@ -0,0 +1,13 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public interface IProcessor +{ + Task ExecuteAsync(CancellationToken stoppingToken); + + /// + /// Easy to switch between background tasks + /// + /// unit: seconds + /// + Task DelayAsync(int delay); +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs index 604def92e..b0378e427 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs @@ -1,21 +1,18 @@ namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; -public abstract record IntegrationEvent : IIntegrationEvent +public abstract record IntegrationEvent(Guid Id, DateTime CreationTime) : IIntegrationEvent { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; [JsonIgnore] - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } + [JsonIgnore] public abstract string Topic { get; set; } public IntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public IntegrationEvent(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs index 7621bd6f3..23aa53e9c 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs @@ -2,11 +2,11 @@ namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; public class IntegrationEventBus : IIntegrationEventBus { - private readonly DispatcherOptions dispatcherOptions; + private readonly DispatcherOptions _dispatcherOptions; private readonly DaprClient _dapr; - private readonly ILogger _logger; + private readonly ILogger? _logger; private readonly IIntegrationEventLogService _eventLogService; - private readonly IOptionsMonitor _appConfig; + private readonly IOptionsMonitor? _appConfig; private readonly string _daprPubsubName; private readonly IEventBus? _eventBus; private readonly IUnitOfWork? _unitOfWork; @@ -14,12 +14,12 @@ public class IntegrationEventBus : IIntegrationEventBus public IntegrationEventBus(IOptions options, DaprClient dapr, IIntegrationEventLogService eventLogService, - IOptionsMonitor appConfig, - ILogger logger, + IOptionsMonitor? appConfig = null, + ILogger? logger = null, IEventBus? eventBus = null, IUnitOfWork? unitOfWork = null) { - dispatcherOptions = options.Value; + _dispatcherOptions = options.Value; _dapr = dapr; _eventLogService = eventLogService; _appConfig = appConfig; @@ -30,9 +30,9 @@ public IntegrationEventBus(IOptions options, } public IEnumerable GetAllEventTypes() => - _eventBus == null ? - dispatcherOptions.GetAllEventTypes() : - dispatcherOptions.GetAllEventTypes().Concat(_eventBus.GetAllEventTypes()).Distinct(); + _eventBus == null + ? _dispatcherOptions.AllEventTypes + : _dispatcherOptions.AllEventTypes.Concat(_eventBus.GetAllEventTypes()).Distinct(); public async Task PublishAsync(TEvent @event) where TEvent : IEvent @@ -54,32 +54,47 @@ public async Task PublishAsync(TEvent @event) private async Task PublishIntegrationAsync(TEvent @event) where TEvent : IIntegrationEvent { - try + if (@event.UnitOfWork == null && _unitOfWork != null) + @event.UnitOfWork = _unitOfWork; + + var topicName = @event.Topic; + if (@event.UnitOfWork != null && !@event.UnitOfWork.UseTransaction) { - if (@event.UnitOfWork == null && _unitOfWork != null) - { - @event.UnitOfWork = _unitOfWork; - } - if (@event.UnitOfWork != null) + try { - _logger.LogInformation("----- Saving changes and integrationEvent: {IntegrationEventId}", @event.Id); - await _eventLogService.SaveEventAsync(@event, @event.UnitOfWork.Transaction); - } + _logger?.LogDebug("----- Saving changes and integrationEvent: {IntegrationEventId}", @event.Id); + await _eventLogService.SaveEventAsync(@event, @event.UnitOfWork!.Transaction); - _logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppId} - ({IntegrationEvent})", @event.Id, _appConfig.CurrentValue.AppId, @event); + _logger?.LogDebug( + "----- Publishing integration event: {IntegrationEventIdPublished} from {AppId} - ({IntegrationEvent})", @event.Id, + _appConfig?.CurrentValue.AppId ?? string.Empty, @event); - await _eventLogService.MarkEventAsInProgressAsync(@event.Id); + await _eventLogService.MarkEventAsInProgressAsync(@event.Id); - var topicName = @event.Topic; - _logger.LogInformation("Publishing event {Event} to {PubsubName}.{TopicName}", @event, _daprPubsubName, topicName); - await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); + _logger?.LogDebug("Publishing event {Event} to {PubsubName}.{TopicName}", @event, _daprPubsubName, topicName); + await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); - await _eventLogService.MarkEventAsPublishedAsync(@event.Id); + await _eventLogService.MarkEventAsPublishedAsync(@event.Id); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", + @event.Id, _appConfig?.CurrentValue.AppId ?? string.Empty, @event); + LocalQueueProcessor.Default.AddJobs(new IntegrationEventLogItem(@event.Id, @event.Topic, @event)); + await _eventLogService.MarkEventAsFailedAsync(@event.Id); + } } - catch (Exception ex) + else { - _logger.LogError(ex, "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", @event.Id, _appConfig.CurrentValue.AppId, @event); - await _eventLogService.MarkEventAsFailedAsync(@event.Id); + await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); } } + + public async Task CommitAsync(CancellationToken cancellationToken = default) + { + if (_unitOfWork is null) + throw new ArgumentNullException(nameof(IUnitOfWork), "You need to UseUoW when adding services"); + + await _unitOfWork.CommitAsync(cancellationToken); + } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs new file mode 100644 index 000000000..66a0074c8 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs @@ -0,0 +1,20 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public class IntegrationEventHostedService : BackgroundService +{ + private readonly ILogger? _logger; + private readonly IProcessingServer _processingServer; + + public IntegrationEventHostedService(IProcessingServer processingServer, ILogger? logger) + { + _logger = logger; + _processingServer = processingServer; + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger?.LogDebug("----- IntegrationEvent background task is starting"); + + return _processingServer.ExecuteAsync(stoppingToken); + } +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs new file mode 100644 index 000000000..2875d237e --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs @@ -0,0 +1,31 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; + +/// +/// Use the local queue to retry sending failed messages +/// +internal class IntegrationEventLogItem +{ + public Guid EventId { get; } + + public string Topic { get; } + + public DateTime CreationTime { get; } + + public int RetryCount { get; private set; } + + public object Event { get; } + + public IntegrationEventLogItem(Guid eventId, string topic, object @event) + { + EventId = eventId; + Topic = topic; + RetryCount = 0; + CreationTime = DateTime.UtcNow; + Event = @event; + } + + public void Retry() + { + this.RetryCount++; + } +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs new file mode 100644 index 000000000..3ffe46da8 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs @@ -0,0 +1,60 @@ +namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; + +internal class LocalQueueProcessor +{ + private readonly ConcurrentDictionary _retryEventLogs; + + public static ILogger? Logger; + public static readonly LocalQueueProcessor Default = new(); + + public LocalQueueProcessor() => _retryEventLogs = new(); + + public static void SetLogger(IServiceCollection services) + { + Logger = services.BuildServiceProvider().GetService>(); + } + + public void AddJobs(IntegrationEventLogItem items) + => _retryEventLogs.TryAdd(items.EventId, items); + + public void RemoveJobs(Guid eventId) + => _retryEventLogs.TryRemove(eventId, out _); + + public void RetryJobs(Guid eventId) + { + if (_retryEventLogs.TryGetValue(eventId, out IntegrationEventLogItem? item)) + { + item.Retry(); + } + } + + public bool IsExist(Guid eventId) + => _retryEventLogs.ContainsKey(eventId); + + public void Delete(int maxRetryTimes) + { + var eventLogItems = _retryEventLogs.Values.Where(log => log.RetryCount >= maxRetryTimes - 1).ToList(); + eventLogItems.ForEach(item => RemoveJobs(item.EventId)); + } + + public List RetrieveEventLogsFailedToPublishAsync(int maxRetryTimes, int retryBatchSize) + { + try + { + return _retryEventLogs + .Select(item => item.Value) + .Where(log => log.RetryCount < maxRetryTimes) + .OrderBy(log => log.RetryCount) + .ThenBy(log => log.CreationTime) + .Take(retryBatchSize) + .ToList(); + } + catch (Exception ex) + { + Logger?.LogWarning(ex, "... getting local retry queue error"); + + Thread.Sleep(TimeSpan.FromSeconds(2)); + return new List(); + } + } +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj index 3fc9a9d3f..5abe0bb1f 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj @@ -7,12 +7,15 @@ - - - - - - + + + + + + + + + diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs index 828cff347..f5905da71 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs @@ -11,15 +11,128 @@ public string PubSubName { if (string.IsNullOrWhiteSpace(value)) { - throw new ArgumentNullException(_pubSubName); + throw new ArgumentNullException(nameof(_pubSubName)); } + _pubSubName = value; } } + /// + /// Local queue maximum number of retries + /// + public int LocalRetryTimes { get; set; } = 3; + + /// + /// maximum number of retries + /// Default is 10 + /// + public int MaxRetryTimes { get; set; } = 10; + + private int _failedRetryInterval = 60; + + /// + /// The interval at which db polls for failure messages. + /// Default is 60 seconds. + /// unit: seconds + /// + public int FailedRetryInterval + { + get => _failedRetryInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(FailedRetryInterval)); + + _failedRetryInterval = value; + } + } + + /// + /// Minimum execution retry interval + /// Default is 60 seconds. + /// + public int MinimumRetryInterval { get; set; } = 60; + + private int _localFailedRetryInterval = 3; + + /// + /// The interval at which the local queue is polled for failed messages. + /// Local queue does not rebuild after service crash + /// Default is 3 seconds. + /// unit: seconds + /// + public int LocalFailedRetryInterval + { + get => _localFailedRetryInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(LocalFailedRetryInterval)); + + _localFailedRetryInterval = value; + } + } + + /// + /// maximum number of retries per retry + /// + public int RetryBatchSize { get; set; } = 100; + + private int _cleaningLocalQueueExpireInterval = 60; + + /// + /// Delete local queue expired event interval + /// Default is 60 seconds + /// unit: seconds + /// + public int CleaningLocalQueueExpireInterval + { + get => _cleaningLocalQueueExpireInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(CleaningLocalQueueExpireInterval)); + + _cleaningLocalQueueExpireInterval = value; + } + } + + private int _cleaningExpireInterval = 300; + + /// + /// Delete expired event interval + /// Default is 300 seconds. + /// unit: seconds + /// + public int CleaningExpireInterval + { + get => _cleaningExpireInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(CleaningExpireInterval)); + + _cleaningExpireInterval = value; + } + } + + /// + /// Expiration time, when the message status is successful and has expired, it will be deleted by the scheduled task + /// Default: ( 24 * 3600 )s + /// + public long PublishedExpireTime { get; set; } = 24 * 3600; + + /// + /// Bulk delete expired messages + /// + public int DeleteBatchCount { get; set; } = 1000; + + public Func? GetCurrentTime { get; set; } = null; + public IServiceCollection Services { get; } - private Assembly[] _assemblies = new Assembly[0]; + private Assembly[] _assemblies = Array.Empty(); public Assembly[] Assemblies { @@ -31,18 +144,15 @@ public Assembly[] Assemblies { throw new ArgumentNullException(nameof(_assemblies)); } - Types = _assemblies.SelectMany(assembly => assembly.GetTypes()).ToList(); - AllEventTypes = GetTypes(typeof(IEvent)).ToList(); + + AllEventTypes = _assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) + .ToList(); } } - private List Types { get; set; } - - private List AllEventTypes { get; set; } - - private IEnumerable GetTypes(Type type) => Types.Where(t => type.IsAssignableFrom(t) && t.IsClass && t != typeof(IntegrationEvent)); - - public IEnumerable GetAllEventTypes() => AllEventTypes; + public List AllEventTypes { get; private set; } public DispatcherOptions(IServiceCollection services) => Services = services; } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs new file mode 100644 index 000000000..f3349ce48 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs @@ -0,0 +1,24 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class DeleteLocalQueueExpiresProcessor : ProcessorBase +{ + private readonly IOptions _options; + + public DeleteLocalQueueExpiresProcessor(IOptions options) + { + _options = options; + } + + /// + /// Delete expired events + /// + /// + /// + public override Task ExecuteAsync(CancellationToken stoppingToken) + { + LocalQueueProcessor.Default.Delete(_options.Value.LocalRetryTimes); + return Task.CompletedTask; + } + + public override int Delay => _options.Value.CleaningLocalQueueExpireInterval; +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs new file mode 100644 index 000000000..cd6d24cf8 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs @@ -0,0 +1,32 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class DeletePublishedExpireEventProcessor : ProcessorBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptions _options; + + public DeletePublishedExpireEventProcessor( + IServiceProvider serviceProvider, + IOptions options) + { + _serviceProvider = serviceProvider; + _options = options; + } + + /// + /// Delete expired events + /// + /// + /// + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var logService = scope.ServiceProvider.GetRequiredService(); + var expireDate = (_options.Value.GetCurrentTime?.Invoke() ?? DateTime.UtcNow).AddSeconds(-_options.Value.PublishedExpireTime); + await logService.DeleteExpiresAsync(expireDate, _options.Value.DeleteBatchCount, stoppingToken); + } + } + + public override int Delay => _options.Value.CleaningExpireInterval; +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs new file mode 100644 index 000000000..fd032471e --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs @@ -0,0 +1,35 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class InfiniteLoopProcessor : ProcessorBase +{ + private readonly IProcessor _processor; + private readonly ILogger? _logger; + + public InfiniteLoopProcessor(IProcessor processor, ILogger? logger = null) + { + _processor = processor; + _logger = logger; + } + + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + await _processor.ExecuteAsync(stoppingToken); + await DelayAsync(((ProcessorBase)_processor).Delay); + } + catch (OperationCanceledException) + { + //ignore + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Processor '{ProcessorName}' failed", _processor.ToString()); + + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + } + } +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs new file mode 100644 index 000000000..2529ada89 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs @@ -0,0 +1,19 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public abstract class ProcessorBase : IProcessor +{ + public abstract Task ExecuteAsync(CancellationToken stoppingToken); + + // /// + // /// Easy to switch between background tasks + // /// + /// unit: seconds + // /// + public Task DelayAsync(int delay) + => Task.Delay(TimeSpan.FromSeconds(delay)); + + /// + /// Task delay time, unit: seconds + /// + public virtual int Delay { get; } +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs new file mode 100644 index 000000000..fafb1dcb2 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs @@ -0,0 +1,76 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class RetryByDataProcessor : ProcessorBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptions _options; + private readonly IOptionsMonitor _appConfig; + private readonly ILogger? _logger; + + public RetryByDataProcessor( + IServiceProvider serviceProvider, + IOptionsMonitor appConfig, + IOptions options, + ILogger? logger = null) + { + _serviceProvider = serviceProvider; + _appConfig = appConfig; + _options = options; + _logger = logger; + } + + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var unitOfWork = scope.ServiceProvider.GetService(); + if (unitOfWork != null) + unitOfWork.UseTransaction = false; + + var dapr = _serviceProvider.GetRequiredService(); + var eventLogService = scope.ServiceProvider.GetRequiredService(); + + var retrieveEventLogs = + await eventLogService.RetrieveEventLogsFailedToPublishAsync(_options.Value.RetryBatchSize, _options.Value.MaxRetryTimes, _options.Value.MinimumRetryInterval); + + foreach (var eventLog in retrieveEventLogs) + { + try + { + if (LocalQueueProcessor.Default.IsExist(eventLog.EventId)) + continue; // The local queue is retrying, no need to retry + + await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId); + + _logger?.LogDebug("Publishing integration event {Event} to {PubsubName}.{TopicName}", eventLog, + _options.Value.PubSubName, + eventLog.Event.Topic); + + await dapr.PublishEventAsync(_options.Value.PubSubName, eventLog.Event.Topic, eventLog.Event, stoppingToken); + + LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); + + await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId); + } + catch (UserFriendlyException) + { + //Update state due to multitasking contention, no processing required + } + catch (Exception ex) + { + _logger?.LogError(ex, + "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", + eventLog.EventId, _appConfig.CurrentValue.AppId, eventLog); + await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); + } + finally + { + if (unitOfWork != null && unitOfWork.TransactionHasBegun) + await unitOfWork.CommitAsync(stoppingToken); + } + } + } + } + + public override int Delay => _options.Value.FailedRetryInterval; +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs new file mode 100644 index 000000000..452cb9b33 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs @@ -0,0 +1,72 @@ +namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class RetryByLocalQueueProcessor : ProcessorBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptionsMonitor _appConfig; + private readonly IOptions _options; + private readonly ILogger? _logger; + + public RetryByLocalQueueProcessor( + IServiceProvider serviceProvider, + IOptionsMonitor appConfig, + IOptions options, + ILogger? logger = null) + { + _serviceProvider = serviceProvider; + _appConfig = appConfig; + _options = options; + _logger = logger; + } + + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var unitOfWork = scope.ServiceProvider.GetService(); + if (unitOfWork != null) + unitOfWork.UseTransaction = false; + + var dapr = _serviceProvider.GetRequiredService(); + var eventLogService = scope.ServiceProvider.GetRequiredService(); + + var retrieveEventLogs = LocalQueueProcessor.Default.RetrieveEventLogsFailedToPublishAsync(_options.Value.LocalRetryTimes, _options.Value.RetryBatchSize); + + foreach (var eventLog in retrieveEventLogs) + { + try + { + LocalQueueProcessor.Default.RetryJobs(eventLog.EventId); + + await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId); + + _logger?.LogDebug( + "Publishing integration event {Event} to {PubsubName}.{TopicName}", + eventLog, + _options.Value.PubSubName, + eventLog.Topic); + + await dapr.PublishEventAsync(_options.Value.PubSubName, eventLog.Topic, eventLog.Event, stoppingToken); + + await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId); + + LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); + } + catch (UserFriendlyException) + { + //Update state due to multitasking contention + LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); + } + catch (Exception ex) + { + _logger?.LogError(ex, + "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", + eventLog.EventId, _appConfig.CurrentValue.AppId, eventLog); + await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); + } + } + } + } + + public override int Delay => _options.Value.LocalFailedRetryInterval; +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md index b41714267..4b2017a7b 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md @@ -1,11 +1,13 @@ +[ไธญ](README.zh-CN.md) | EN + ## IntegrationEventBus -Example๏ผš +Example: ```C# Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr //Send cross-process messages Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //Record cross-process message logs -Install-Package MASA.Contrib.Data.Uow.EF //Use UnitOfWork +Install-Package MASA.Contrib.Data.UoW.EF //Use UnitOfWork ``` 1. Add IIntegrationEventBus @@ -14,7 +16,7 @@ Install-Package MASA.Contrib.Data.Uow.EF //Use UnitOfWork builder.Services .AddDaprEventBus(options=> { - options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity")) + options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity")) .UseEventLog(); ) }); @@ -62,4 +64,41 @@ public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) { //todo } -``` \ No newline at end of file +``` + +### retry policy + +```C# +builder.Services + .AddDaprEventBus(options=> + { + // options.MaxRetryTimes = 50;//Maximum number of retries, default: 50 + // options.RetryBatchSize = 100;//Number of single retry events, used to get retry events from persistent data source, default 100 + // options.FailedRetryInterval = 60;//Persistent data source retry pause interval, default 60s + // options.CleaningExpireInterval = 300;//Clearing expired event pause interval, unit: s, default 300s + // options.ExpireDate = 24 * 3600;//Expiration time, CreationTime + ExpireDate = Expiration time, default 1 day + + // options.LocalFailedRetryInterval = 3;//Local queue retry pause interval, default 3s + // options.CleaningLocalQueueExpireInterval = 60;//Clearing local queue expired event pause interval, unit: s, default 60s + }); +``` + +Retry is divided into local queue retry and retry from persistent data source: + +local queue: + +Features: +- Short retry interval, support second-level retry interval +- Get data from memory, faster +- After the system crashes, the previous local queue will not be rebuilt, and will be automatically demoted to the persistent queue to retry the task + +Persistent data source queue: + +Features: + +- After the system crashes, the retry queue can be obtained from db or other persistent sources to ensure 100% retry of events +- As a downgrade solution for local memory queues, lower pressure on db or other data sources + +In the case of a single copy, the tasks of the two queues will only be executed in a single queue, and there will be no simultaneous execution of the two queues. +In the case of multiple copies, the same task may be executed by multiple copies. Although we have made idempotent, but the delivery guarantee is At Least Once, it is still possible that the event publishing is successful, but the state change fails. +At this point, the event may be re-sent. We recommend that the task executor retry across events. \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md new file mode 100644 index 000000000..af9d84520 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md @@ -0,0 +1,105 @@ +ไธญ | [EN](README.md) + +## IntegrationEventBus + +็”จไพ‹๏ผš + +```C# +Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr //ๅ‘้€่ทจ่ฟ›็จ‹ๆถˆๆฏ +Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //่ฎฐๅฝ•่ทจ่ฟ›็จ‹ๆถˆๆฏๆ—ฅๅฟ— +Install-Package MASA.Contrib.Data.UoW.EF //ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ +``` + +1. ๆทปๅŠ IIntegrationEventBus + +```C# +builder.Services + .AddDaprEventBus(options=> + { + options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"))//ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ๏ผŒๆŽจ่ไฝฟ็”จ + .UseEventLog(); + ) + }); +``` + +> CustomerDbContext ้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext + +2. ่‡ชๅฎšไน‰ IntegrationEvent + +```C# +public class DemoIntegrationEvent : IntegrationEvent +{ + public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name + + //todo ่‡ชๅฎšไน‰ๅฑžๆ€งๅ‚ๆ•ฐ +} +``` + +3. ่‡ชๅฎšไน‰CustomDbContext + +```C# +public class CustomDbContext : IntegrationEventLogContext +{ + public DbSet Users { get; set; } = null!; + + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + + } +} +``` + +4. ๅ‘้€ Event + +```C# +IIntegrationEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIIntegrationEventBus +await eventBus.PublishAsync(new DemoIntegrationEvent());//ๅ‘้€่ทจ่ฟ›็จ‹ไบ‹ไปถ +``` + +5. ่ฎข้˜…ไบ‹ไปถ + +```C# +[Topic("pubsub", nameof(DomeIntegrationEvent))] +public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) +{ + //todo +} +``` + +### ้‡่ฏ•็ญ–็•ฅ + +```C# +builder.Services + .AddDaprEventBus(options=> + { + // options.MaxRetryTimes = 50;//ๆœ€ๅคง้‡่ฏ•ๆฌกๆ•ฐ, ้ป˜่ฎค๏ผš50 + // options.RetryBatchSize = 100;//ๅ•ๆฌก้‡่ฏ•ไบ‹ไปถๆ•ฐ้‡, ็”จไบŽไปŽๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ่Žทๅ–ๅพ…้‡่ฏ•ไบ‹ไปถ, ้ป˜่ฎค100 + // options.FailedRetryInterval = 60;//ๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้‡่ฏ•ๅœๆญ‡้—ด้š”, ้ป˜่ฎค60s + // options.CleaningExpireInterval = 300;//ๆธ…้™คๅทฒ่ฟ‡ๆœŸไบ‹ไปถๅœๆญ‡้—ด้š”๏ผŒๅ•ไฝ๏ผšs, ้ป˜่ฎค 300s + // options.ExpireDate = 24 * 3600;//่ฟ‡ๆœŸๆ—ถ้—ด๏ผŒCreationTime + ExpireDate = ่ฟ‡ๆœŸๆ—ถ้—ด, ้ป˜่ฎค1ๅคฉ + + // options.LocalFailedRetryInterval = 3;//ๆœฌๅœฐ้˜Ÿๅˆ—้‡่ฏ•ๅœๆญ‡้—ด้š”, ้ป˜่ฎค3s + // options.CleaningLocalQueueExpireInterval = 60;//ๆธ…้™คๆœฌๅœฐ้˜Ÿๅˆ—ๅทฒ่ฟ‡ๆœŸไบ‹ไปถๅœๆญ‡้—ด้š”๏ผŒๅ•ไฝ๏ผšs, ้ป˜่ฎค 60s + }); +``` + +้‡่ฏ•ๅˆ†ไธบๆœฌๅœฐ้˜Ÿๅˆ—้‡่ฏ•ไปฅๅŠไปŽๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้‡่ฏ•ไธค็ง๏ผš + +ๆœฌๅœฐ้˜Ÿๅˆ—๏ผš + +็‰น็‚น๏ผš +- ้‡่ฏ•้—ด้š”็Ÿญ๏ผŒๆ”ฏๆŒ็ง’็บงๅˆซ้‡่ฏ•้—ด้š” +- ไปŽๅ†…ๅญ˜่Žทๅ–ๆ•ฐๆฎ๏ผŒ้€Ÿๅบฆๆ›ดๅฟซ +- ็ณป็ปŸๅดฉๆบƒๅŽ๏ผŒไน‹ๅ‰็š„ๆœฌๅœฐ้˜Ÿๅˆ—ไธไผš้‡ๅปบ๏ผŒ่‡ชๅŠจ้™็บงๅˆฐๆŒไน…ๅŒ–้˜Ÿๅˆ—ไธญ้‡่ฏ•ไปปๅŠก + +ๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้˜Ÿๅˆ—๏ผš + +็‰น็‚น๏ผš + +- ็ณป็ปŸๅดฉๆบƒๅŽ๏ผŒๅฏไปฅไปŽdbๆˆ–่€…ๅ…ถไป–ๆŒไน…ๅŒ–ๆบ่Žทๅ–้‡่ฏ•้˜Ÿๅˆ—๏ผŒ็กฎไฟไบ‹ไปถ100%้‡่ฏ• +- ไฝœไธบๆœฌๅœฐๅ†…ๅญ˜้˜Ÿๅˆ—็š„้™็บงๆ–นๆกˆ๏ผŒๅฏนdbๆˆ–่€…ๅ…ถไป–ๆ•ฐๆฎๆบๅŽ‹ๅŠ›ๆ›ดไฝŽ + +ๅœจๅ•ๅ‰ฏๆœฌๆƒ…ๅ†ตไธ‹๏ผŒไธค็ง้˜Ÿๅˆ—็š„ไปปๅŠกไป…ไผšๅœจๅ•ไธช้˜Ÿๅˆ—ไธญๆ‰ง่กŒ๏ผŒไธไผšๅญ˜ๅœจไธคไธช้˜Ÿๅˆ—ๅŒๆ—ถๆ‰ง่กŒ็š„ๆƒ…ๅ†ตใ€‚ +ๅœจๅคšๅ‰ฏๆœฌๆƒ…ๅ†ตไธ‹๏ผŒๅŒไธ€ไธชไปปๅŠกๅฏ่ƒฝไผš่ขซๅคšไธชๅ‰ฏๆœฌๆ‰€ๆ‰ง่กŒ๏ผŒ่™ฝ็„ถๆˆ‘ไปฌๆœ‰ๅšๅน‚็ญ‰๏ผŒไฝ†ไธบไบคไป˜ไฟ่ฏๆ˜ฏ At Least Once๏ผŒไป็„ถๆœ‰ๅฏ่ƒฝๅ‡บ็Žฐไบ‹ไปถๅ‘ๅธƒๆˆๅŠŸ๏ผŒไฝ†็Šถๆ€ๆ›ดๆ”นๅคฑ่ดฅ็š„ๆƒ…ๅ†ต๏ผŒ +ๆญคๆ—ถไบ‹ไปถๅฏ่ƒฝไผš้‡ๅ‘๏ผŒๆˆ‘ไปฌๅปบ่ฎฎไปปๅŠกๆ‰ง่กŒ่€…ๅšๅฅฝๅฏน่ทจไบ‹ไปถ็š„้‡่ฏ• + diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-cn.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-cn.md deleted file mode 100644 index ba1a819e2..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-cn.md +++ /dev/null @@ -1,65 +0,0 @@ -## IntegrationEventBus - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr //ๅ‘้€่ทจ่ฟ›็จ‹ๆถˆๆฏ -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //่ฎฐๅฝ•่ทจ่ฟ›็จ‹ๆถˆๆฏๆ—ฅๅฟ— -Install-Package MASA.Contrib.Data.Uow.EF //ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ -``` - -1. ๆทปๅŠ IIntegrationEventBus - -```C# -builder.Services - .AddDaprEventBus(options=> - { - options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"))//ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ๏ผŒๆŽจ่ไฝฟ็”จ - .UseEventLog(); - ) - }); -``` - -> CustomerDbContext ้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext - -2. ่‡ชๅฎšไน‰ IntegrationEvent - -```C# -public class DemoIntegrationEvent : IntegrationEvent -{ - public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name - - //todo ่‡ชๅฎšไน‰ๅฑžๆ€งๅ‚ๆ•ฐ -} -``` - -3. ่‡ชๅฎšไน‰CustomDbContext - -```C# -public class CustomDbContext : IntegrationEventLogContext -{ - public DbSet Users { get; set; } = null!; - - public CustomDbContext(MasaDbContextOptions options) : base(options) - { - - } -} -``` - -4. ๅ‘้€ Event - -```C# -IIntegrationEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIIntegrationEventBus -await eventBus.PublishAsync(new DemoIntegrationEvent());//ๅ‘้€่ทจ่ฟ›็จ‹ไบ‹ไปถ -``` - -5. ่ฎข้˜…ไบ‹ไปถ - -```C# -[Topic("pubsub", nameof(DomeIntegrationEvent))] -public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) -{ - //todo -} -``` \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs new file mode 100644 index 000000000..20a92ec81 --- /dev/null +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs @@ -0,0 +1,20 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; + +public class DefaultHostedService : IProcessingServer +{ + private readonly IEnumerable _processors; + private readonly ILogger? _logger; + + public DefaultHostedService(IEnumerable processors, ILogger? logger = null) + { + _processors = processors; + _logger = logger; + } + + public Task ExecuteAsync(CancellationToken stoppingToken) + { + var processorTasks = _processors.Select(processor => new InfiniteLoopProcessor(processor, _logger)) + .Select(process => process.ExecuteAsync(stoppingToken)); + return Task.WhenAll(processorTasks); + } +} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs index 6aed6ff7f..5281efc2c 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs @@ -3,9 +3,9 @@ namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; public static class ServiceCollectionExtensions { public static IServiceCollection AddDaprEventBus( - this IServiceCollection services, - Action? options = null) - where TIntegrationEventLogService : class, IIntegrationEventLogService + this IServiceCollection services, + Action? options = null) + where TIntegrationEventLogService : class, IIntegrationEventLogService => services.TryAddDaprEventBus(null, options); internal static IServiceCollection TryAddDaprEventBus( @@ -14,28 +14,40 @@ internal static IServiceCollection TryAddDaprEventBus? options = null) where TIntegrationEventLogService : class, IIntegrationEventLogService { - if (services.Any(service => service.ImplementationType == typeof (IntegrationEventBusProvider))) return services; + if (services.Any(service => service.ImplementationType == typeof(IntegrationEventBusProvider))) + return services; + services.AddSingleton(); var dispatcherOptions = new DispatcherOptions(services); options?.Invoke(dispatcherOptions); + if (dispatcherOptions.Assemblies.Length == 0) - { dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); - } - services.TryAddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); - services.AddLogging(); + services.TryAddSingleton(typeof(IOptions), + serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); + LocalQueueProcessor.SetLogger(services); services.AddDaprClient(builder); services.AddScoped(); services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.TryAddSingleton(); + services.AddHostedService(); + if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) + { + var logger = services.BuildServiceProvider().GetService>(); + logger?.LogWarning("UoW is not enabled, local messages will not be integrated"); + } return services; } private class IntegrationEventBusProvider { - } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs index 4a7056617..1c635a31f 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs @@ -1,13 +1,19 @@ global using Dapr.Client; -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; +global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; +global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; +global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; global using MASA.Utils.Models.Config; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; +global using System.Collections.Concurrent; global using System.Reflection; global using System.Text.Json.Serialization; + diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs index 712aae5d5..7fe638666 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs @@ -14,19 +14,15 @@ public static IDispatcherOptions UseEventLog( Action optionsBuilder) { if (options.Services == null) - { throw new ArgumentNullException(nameof(options.Services)); - } - if(optionsBuilder == null) - { + if (optionsBuilder == null) throw new ArgumentNullException(nameof(optionsBuilder)); - } - if (options.Services.Any(service => service.ImplementationType == typeof (EventLogProvider))) return options; + if (options.Services.Any(service => service.ImplementationType == typeof(EventLogProvider))) return options; options.Services.AddSingleton(); - DbContextExtensions.AddCustomMasaDbContext(options.Services, optionsBuilder); + options.Services.AddCustomMasaDbContext(optionsBuilder); return options; } @@ -41,16 +37,13 @@ public static IDispatcherOptions UseEventLog( this IDispatcherOptions options) where TDbContext : IntegrationEventLogContext { if (options.Services == null) - { throw new ArgumentNullException(nameof(options.Services)); - } if (typeof(TDbContext) == typeof(IntegrationEventLogContext)) - { - throw new NotSupportedException($"{typeof(TDbContext).FullName} must be IntegrationEventLogContext derived classes, or using UseEventLog() replace UseEventLog<{typeof(TDbContext).FullName}>()"); - } + throw new NotSupportedException( + $"{typeof(TDbContext).FullName} must be IntegrationEventLogContext derived classes, or using UseEventLog() replace UseEventLog<{typeof(TDbContext).FullName}>()"); - if (options.Services.Any(service => service.ImplementationType == typeof (EventLogProvider))) return options; + if (options.Services.Any(service => service.ImplementationType == typeof(EventLogProvider))) return options; options.Services.AddSingleton(); options.Services.TryAddScoped(serviceProvider => serviceProvider.GetRequiredService()); @@ -59,6 +52,5 @@ public static IDispatcherOptions UseEventLog( private class EventLogProvider { - } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs index 57defda70..83f53bcf5 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs @@ -32,13 +32,22 @@ private void ConfigureEventLogEntry(EntityTypeBuilder build builder.Property(e => e.CreationTime) .IsRequired(); + builder.Property(e => e.ModificationTime) + .IsRequired(); + builder.Property(e => e.State) .IsRequired(); builder.Property(e => e.TimesSent) .IsRequired(); + builder.Property(e => e.RowVersion) + .IsRowVersion(); + builder.Property(e => e.EventTypeName) .IsRequired(); + + builder.HasIndex(e => new { e.State, e.ModificationTime },"index_state_modificationtime"); + builder.HasIndex(e => new { e.State, e.TimesSent, e.ModificationTime },"index_state_timessent_modificationtime"); } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs index 480d65c0b..29a04682f 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs @@ -4,66 +4,155 @@ public class IntegrationEventLogService : IIntegrationEventLogService { private readonly IntegrationEventLogContext _eventLogContext; private readonly IServiceProvider _serviceProvider; - private IEnumerable _eventTypes; + private readonly Logger? _logger; + private IEnumerable? _eventTypes; - public IntegrationEventLogService(IntegrationEventLogContext eventLogContext, IServiceProvider serviceProvider) + public IntegrationEventLogService( + IntegrationEventLogContext eventLogContext, + IServiceProvider serviceProvider, + Logger? logger = null) { _eventLogContext = eventLogContext; _serviceProvider = serviceProvider; + _logger = logger; } - public async Task> RetrieveEventLogsPendingToPublishAsync(Guid transactionId) + /// + /// Get messages to retry + /// + /// maximum number of retries per retry + /// + /// default: 60s + /// + public async Task> RetrieveEventLogsFailedToPublishAsync(int retryBatchSize = 200, int maxRetryTimes = 10, int minimumRetryInterval = 60) { + //todo: Subsequent acquisition of the current time needs to be uniformly replaced with the unified time method provided by the framework, which is convenient for subsequent uniform replacement to UTC time or other urban time. The default setting here is Utc time. + var time = DateTime.UtcNow.AddSeconds(-minimumRetryInterval); var result = await _eventLogContext.EventLogs - .Where(e => e.TransactionId == transactionId && e.State == IntegrationEventStates.NotPublished).ToListAsync(); + .Where(e => (e.State == IntegrationEventStates.PublishedFailed || e.State == IntegrationEventStates.InProgress) && + e.TimesSent <= maxRetryTimes && + e.ModificationTime < time) + .OrderBy(o => o.CreationTime) + .Take(retryBatchSize) + .ToListAsync(); if (result.Any()) { - if (_eventTypes == null) - { - _eventTypes = _serviceProvider.GetRequiredService().GetAllEventTypes().Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type)); - } + _eventTypes ??= _serviceProvider.GetRequiredService().GetAllEventTypes() + .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type)); + return result.OrderBy(o => o.CreationTime) .Select(e => e.DeserializeJsonContent(_eventTypes.First(t => t.Name == e.EventTypeShortName))); } - return new List(); + return result; } public async Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) { - if (transaction == null) throw new ArgumentNullException(nameof(transaction)); + if (transaction == null) + throw new ArgumentNullException(nameof(transaction)); + if (_eventLogContext.Database.CurrentTransaction == null) - _eventLogContext.Database.UseTransaction(transaction, Guid.NewGuid()); + await _eventLogContext.Database.UseTransactionAsync(transaction, Guid.NewGuid()); + var eventLogEntry = new IntegrationEventLog(@event, _eventLogContext.Database.CurrentTransaction!.TransactionId); await _eventLogContext.EventLogs.AddAsync(eventLogEntry); await _eventLogContext.SaveChangesAsync(); + + CheckAndDetached(eventLogEntry); } public Task MarkEventAsPublishedAsync(Guid eventId) { - return UpdateEventStatus(eventId, IntegrationEventStates.Published); + return UpdateEventStatus(eventId, IntegrationEventStates.Published, eventLog => + { + if (eventLog.State != IntegrationEventStates.InProgress) + { + _logger?.LogWarning( + "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", + IntegrationEventStates.Published, eventLog.State, eventLog.Id); + throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.Published}, the current State is {eventLog.State}, Id: {eventLog.Id}"); + } + }); } public Task MarkEventAsInProgressAsync(Guid eventId) { - return UpdateEventStatus(eventId, IntegrationEventStates.InProgress); + return UpdateEventStatus(eventId, IntegrationEventStates.InProgress, eventLog => + { + if (eventLog.State != IntegrationEventStates.NotPublished && eventLog.State != IntegrationEventStates.PublishedFailed) + { + _logger?.LogWarning( + "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", + IntegrationEventStates.InProgress, eventLog.State, eventLog.Id); + throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.InProgress}, the current State is {eventLog.State}, Id: {eventLog.Id}"); + } + }); } public Task MarkEventAsFailedAsync(Guid eventId) { - return UpdateEventStatus(eventId, IntegrationEventStates.PublishedFailed); + return UpdateEventStatus(eventId, IntegrationEventStates.PublishedFailed, eventLog => + { + if (eventLog.State != IntegrationEventStates.InProgress) + { + _logger?.LogWarning( + "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", + IntegrationEventStates.PublishedFailed, eventLog.State, eventLog.Id); + throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.PublishedFailed}, the current State is {eventLog.State}, Id: {eventLog.Id}"); + } + }); } - private Task UpdateEventStatus(Guid eventId, IntegrationEventStates status) + public async Task DeleteExpiresAsync(DateTime expiresAt, int batchCount = 1000, CancellationToken token = default) { - var eventLogEntry = _eventLogContext.EventLogs.Single(e => e.Id == eventId); - eventLogEntry.State = status; + var eventLogs = _eventLogContext.EventLogs.Where(e => e.ModificationTime < expiresAt && e.State == IntegrationEventStates.Published) + .OrderBy(e => e.CreationTime).Take(batchCount); + _eventLogContext.EventLogs.RemoveRange(eventLogs); + await _eventLogContext.SaveChangesAsync(token); + if (_eventLogContext.ChangeTracker.QueryTrackingBehavior != QueryTrackingBehavior.TrackAll) + { + foreach (var log in eventLogs) + { + _eventLogContext.Entry(log).State = EntityState.Detached; + } + } + } + + private async Task UpdateEventStatus(Guid eventId, IntegrationEventStates status, Action? action = null) + { + var eventLogEntry = _eventLogContext.EventLogs.FirstOrDefault(e => e.EventId == eventId); + if (eventLogEntry == null) + throw new ArgumentException(nameof(eventId)); + + action?.Invoke(eventLogEntry); + + + eventLogEntry.State = status; + eventLogEntry.ModificationTime = eventLogEntry.GetCurrentTime(); if (status == IntegrationEventStates.InProgress) eventLogEntry.TimesSent++; - _eventLogContext.EventLogs.Update(eventLogEntry); - return _eventLogContext.SaveChangesAsync(); + try + { + _eventLogContext.EventLogs.Update(eventLogEntry); + await _eventLogContext.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException ex) + { + throw new UserFriendlyException(ex.Message); + } + + CheckAndDetached(eventLogEntry); + } + + private void CheckAndDetached(IntegrationEventLog integrationEvent) + { + if (_eventLogContext.ChangeTracker.QueryTrackingBehavior != QueryTrackingBehavior.TrackAll) + { + _eventLogContext.Entry(integrationEvent).State = EntityState.Detached; + } } } diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs index 4b32ab4ad..c21f782a4 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs @@ -1,6 +1,3 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using System.Linq.Expressions; - namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; internal abstract class QueryFilterProvider : IQueryFilterProvider diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj index 2c25ed79e..c4d5d2b96 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj @@ -7,10 +7,14 @@ - - - - + + + + + + + + diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md index 86e87c0b1..4ab7a3c7d 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md @@ -1,3 +1,5 @@ +[ไธญ](README.zh-CN.md) | EN + ## MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF > Provide support for sending IntegrationEvent diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-cn.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md similarity index 94% rename from src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-cn.md rename to src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md index 5dfeb3634..76784f8b5 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-cn.md +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md @@ -1,3 +1,5 @@ +ไธญ | [EN](README.md) + ## MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF > ไธบๅ‘้€IntegrationEventๆไพ›ๆ”ฏๆŒ @@ -19,4 +21,4 @@ Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF } ``` -> ๆ็คบ๏ผšCustomDbContext้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext \ No newline at end of file +> ๆ็คบ๏ผšCustomDbContext้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs index 18e5a61e2..260da3f76 100644 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs +++ b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs @@ -5,11 +5,14 @@ global using MASA.Utils.Data.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Microsoft.EntityFrameworkCore.Metadata; global using Microsoft.EntityFrameworkCore.Metadata.Builders; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; global using System; global using System.Collections.Generic; global using System.Data.Common; global using System.Linq; +global using System.Linq.Expressions; global using System.Threading.Tasks; diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs index 2253a8817..0ba4e22b0 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs @@ -1,16 +1,15 @@ namespace MASA.Contrib.ReadWriteSpliting.CQRS.Commands; -public record Command : ICommand +public record Command(Guid Id, DateTime CreationTime) : ICommand { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; - public Command() : this(Guid.NewGuid(), DateTime.UtcNow) { } + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } - public Command(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } + public Command() : this(Guid.NewGuid(), DateTime.UtcNow) { } } diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj index 70f79c30a..21b30680e 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj @@ -7,10 +7,13 @@ - - - - + + - + + + + + + diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs index d7e3c2455..44fac69fd 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs @@ -1,19 +1,15 @@ namespace MASA.Contrib.ReadWriteSpliting.CQRS.Queries; -public abstract record Query : IQuery +public abstract record Query(Guid Id, DateTime CreationTime) : IQuery where TResult : notnull { - public Guid Id { get; init; } + [JsonIgnore] + public Guid Id { get; } = Id; - public DateTime CreationTime { get; init; } + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; public abstract TResult Result { get; set; } public Query() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public Query(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } } diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs index 5e44c2eba..44256ba62 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs @@ -1,13 +1,8 @@ namespace MASA.Contrib.ReadWriteSpliting.CQRS.Queries; -public abstract class QueryHandler : IQueryHandler, ISagaEventHandler - where TCommand : IQuery +public abstract class QueryHandler : IQueryHandler + where TQuery : IQuery where TResult : notnull { - public abstract Task HandleAsync(TCommand @event); - - public virtual Task CancelAsync(TCommand @event) - { - return Task.CompletedTask; - } + public abstract Task HandleAsync(TQuery @event); } diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md index 9ef98153e..a2cb52a95 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md @@ -1,3 +1,5 @@ +[ไธญ](README.zh-CN.md) | EN + ## CQRS Example๏ผš @@ -18,9 +20,9 @@ Install-Package MASA.Contrib.ReadWriteSpliting.CQRS ```C# public class CatalogItemQuery : Query> { - public string Name { get; set; } = default!; + public string Name { get; set; } = default!; - public override List Result { get; set; } = default!; + public override List Result { get; set; } = default!; } ``` @@ -56,7 +58,7 @@ await eventBus.PublishAsync(new CatalogItemQuery() { Name = "Rolex" }); ```c# public class CreateCatalogItemCommand : Command { - public string Name { get; set; } = default!; + public string Name { get; set; } = default!; //todo } diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-cn.md b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md similarity index 89% rename from src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-cn.md rename to src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md index b58e20354..6b84aebe2 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-cn.md +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md @@ -1,3 +1,5 @@ +ไธญ | [EN](README.md) + ## CQRS ็”จไพ‹๏ผš @@ -18,9 +20,9 @@ Install-Package MASA.Contrib.ReadWriteSpliting.CQRS ```C# public class CatalogItemQuery : Query> { - public string Name { get; set; } = default!; + public string Name { get; set; } = default!; - public override List Result { get; set; } = default!; + public override List Result { get; set; } = default!; } ``` @@ -56,7 +58,7 @@ await eventBus.PublishAsync(new CatalogItemQuery() { Name = "Rolex" }); ```c# public class CreateCatalogItemCommand : Command { - public string Name { get; set; } = default!; + public string Name { get; set; } = default!; //todo } @@ -83,4 +85,4 @@ public class CatalogCommandHandler : CommandHandler ```C# IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus await eventBus.PublishAsync(new CreateCatalogItemCommand()); -``` \ No newline at end of file +``` diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs index c934201ef..79632aa72 100644 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs +++ b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs @@ -1,3 +1,6 @@ +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.ReadWriteSpliting.CQRS.Commands; global using MASA.BuildingBlocks.ReadWriteSpliting.CQRS.Queries; +global using System.Text.Json.Serialization; + diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj b/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj index b8dd6b327..0c88a1c69 100644 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj +++ b/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj @@ -6,9 +6,13 @@ - - - + + + + + + + diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md b/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md index 0a280b03d..6560eea7f 100644 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md +++ b/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md @@ -1,3 +1,5 @@ +[ไธญ](README.zh-CN.md) | EN + ## MinimalAPI Original usage๏ผš @@ -20,7 +22,7 @@ Install-Package MASA.Contrib.Service.MinimalAPIs ```c# var builder = WebApplication.CreateBuilder(args); var app = builder.Services - .AddServices(builder); + .AddServices(builder); ``` 2. Customize Service and inherit ServiceBase @@ -28,7 +30,7 @@ var app = builder.Services ```c# public class IntegrationEventService : ServiceBase { - public IntegrationEventService(IServiceCollection services) : base(services) + public IntegrationEventService(IServiceCollection services) : base(services) { App.MapGet("/api/v1/payment/HelloWorld", HelloWorld); } diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-cn.md b/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md similarity index 85% rename from src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-cn.md rename to src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md index 9cb242df1..ef8c8bca7 100644 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-cn.md +++ b/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md @@ -1,3 +1,5 @@ +ไธญ | [EN](README.md) + ## MinimalAPI ๅŽŸๅง‹็”จๆณ•๏ผš @@ -20,7 +22,7 @@ Install-Package MASA.Contrib.Service.MinimalAPIs ```c# var builder = WebApplication.CreateBuilder(args); var app = builder.Services - .AddServices(builder); + .AddServices(builder); ``` 2. ่‡ชๅฎšไน‰Serviceๅนถ็ปงๆ‰ฟServiceBase๏ผŒๅฆ‚๏ผš @@ -28,7 +30,7 @@ var app = builder.Services ```c# public class IntegrationEventService : ServiceBase { - public IntegrationEventService(IServiceCollection services) : base(services) + public IntegrationEventService(IServiceCollection services) : base(services) { App.MapGet("/api/v1/payment/HelloWorld", HelloWorld); } diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs b/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs index 910cee7a0..2ef353c06 100644 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs +++ b/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs @@ -1,10 +1,13 @@ namespace MASA.Contrib.Service.MinimalAPIs; -public class ServiceBase : IService + +public abstract class ServiceBase : IService { private ServiceProvider _serviceProvider = default!; public WebApplication App => _serviceProvider.GetRequiredService(); + public string BaseUri { get; } + public IServiceCollection Services { get; protected set; } public ServiceBase(IServiceCollection services) @@ -13,9 +16,104 @@ public ServiceBase(IServiceCollection services) _serviceProvider = services.BuildServiceProvider(); } + public ServiceBase(IServiceCollection services, string baseUri) + { + BaseUri = baseUri; + Services = services; + _serviceProvider = services.BuildServiceProvider(); + } + public TService? GetService() => _serviceProvider.GetService(); public TService GetRequiredService() where TService : notnull => Services.BuildServiceProvider().GetRequiredService(); -} + + #region Map GET,POST,PUT,DELETE + + /// + /// Adds a to the that matches HTTP GET requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapGet(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapGet(pattern, handler); + } + + /// + /// Adds a to the that matches HTTP POST requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapPost(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapPost(pattern, handler); + } + + /// + /// Adds a to the that matches HTTP PUT requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapPut(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapPut(pattern, handler); + } + + /// + /// Adds a to the that matches HTTP DELETE requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapDelete(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapDelete(pattern, handler); + } + + private static string FormatAction(string action, bool trimEndAsync) + { + if (trimEndAsync && action.EndsWith("Async")) + { + var i = action.LastIndexOf("Async", StringComparison.Ordinal); + action = action[..i]; + } + + return action; + } + + private static string CombineUris(params string[] uris) + { + return string.Join("/", uris.Select(u => u.Trim('/'))); + } + + #endregion +} \ No newline at end of file diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs b/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs index b6c816c19..98a21e7a1 100644 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs +++ b/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs @@ -1,9 +1,16 @@ -using System.Linq; - namespace MASA.Contrib.Service.MinimalAPIs; public static class ServiceCollectionExtensions { + /// + /// Add all classes that inherit from ServiceBase to Microsoft.Extensions.DependencyInjection.IServiceCollection + /// Notice: this method must be last call. + /// + /// The Microsoft.AspNetCore.Builder.WebApplicationBuilder. + /// + public static WebApplication AddServices(this WebApplicationBuilder builder) + => builder.Services.AddServices(builder); + /// /// Add all classes that inherit from ServiceBase to Microsoft.Extensions.DependencyInjection.IServiceCollection /// Notice: this method must be last call. @@ -21,7 +28,7 @@ public static WebApplication AddServices(this IServiceCollection services, WebAp services.AddSingleton(new Lazy(() => builder.Build(), LazyThreadSafetyMode.ExecutionAndPublication)) .AddTransient(serviceProvider => serviceProvider.GetRequiredService>().Value); - services.AddServices(true); + services.AddServices(true, AppDomain.CurrentDomain.GetAssemblies()); } var serviceProvider = services.BuildServiceProvider(); diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs b/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs index e4101ab7d..a6b33566f 100644 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs +++ b/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs @@ -1,6 +1,8 @@ global using MASA.BuildingBlocks.Service.MinimalAPIs; global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Routing; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using System; +global using System.Linq; global using System.Threading; diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs new file mode 100644 index 000000000..a8bc038b9 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs @@ -0,0 +1,388 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests; + +[TestClass] +public class DccClientTest +{ + private Mock _client; + private IServiceCollection _services; + private IServiceProvider _serviceProvider => _services.BuildServiceProvider(); + private JsonSerializerOptions _jsonSerializerOptions; + private DccSectionOptions _dccSectionOptions; + private CustomTrigger _trigger; + + [TestInitialize] + public void Initialize() + { + _client = new Mock(); + _services = new ServiceCollection(); + _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + _dccSectionOptions = new DccSectionOptions() + { + Environment = "Test", + Cluster = "Default", + AppId = "DccTest", + ConfigObjects = new List() + { + "Test1" + }, + Secret = "" + }; + _trigger = new CustomTrigger(_jsonSerializerOptions); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsync(string environment, string cluster, string appId, string configObject) + { + Action valueChanged = delegate (string? val) { }; + _client.Setup(client => client.GetAsync(It.IsAny(), valueChanged).Result).Returns(() => null).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, valueChanged), "configObject invalid" + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => "test").Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => "{}").Verifiable(); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), "configObject invalid" + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new + { + ConfigFormat = "1", + Content = "" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), "configObject invalid" + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease + { + ConfigFormat = (ConfigFormats)5, + Content = "" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + await Assert.ThrowsExceptionAsync( + async () => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), + "Unsupported configuration type"); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsyncByJson(string environment, string cluster, string appId, string configObject) + { + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + + var brand = new Brands("Apple"); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Raw == brand.Serialize(_jsonSerializerOptions)); + Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Json); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsyncByText(string environment, string cluster, string appId, string configObject) + { + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = "test" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Raw == "test"); + Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Text); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsyncByProperty(string environment, string cluster, string appId, string configObject) + { + List properties = new List() + { + new() + { + Key = "Brand", + Value = "Microsoft" + } + }; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = properties.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Raw == properties.Serialize(_jsonSerializerOptions)); + Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Text); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetAsyncByJson(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + var newBrand = new Brands("Microsoft2"); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetAsync(environment, cluster, appId, configObject, (Brands br) => + { + Assert.IsTrue(br.Id == newBrand.Id); + Assert.IsTrue(br.Name == newBrand.Name); + }); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Serialize(_jsonSerializerOptions).Equals(brand.Serialize(_jsonSerializerOptions))); + _trigger.Execute(); + + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id == newBrand.Id && ret.Name == newBrand.Name); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + newBrand.Name = "Masa"; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id && ret.Name == brand.Name); + _trigger.Execute(); + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Id == newBrand.Id && ret.Name == "Masa"); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id && ret.Name == brand.Name); + + Initialize(); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => + { + await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + }); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetAsyncByText(string environment, string cluster, string appId, string configObject) + { + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = "test" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => + { + await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + }); + + Initialize(); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = "1" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + Assert.IsTrue(await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()) == 1); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetAsyncByProperty(string environment, string cluster, string appId, string configObject) + { + var brand = new List() + { + new() + { + Key = "Id", + Value = Guid.NewGuid().ToString(), + }, + new() + { + Key = "Name", + Value = "Microsoft" + } + }; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Properties, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id.ToString() == brand.Where(b => b.Key == "Id").Select(t => t.Value).FirstOrDefault() && + ret.Name == brand.Where(b => b.Key == "Name").Select(t => t.Value).FirstOrDefault()); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetDynamicAsyncByJson(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + var newBrand = new Brands("Microsoft2"); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, (dynamic obj) => + { + Assert.IsTrue((obj.Id + "") == newBrand.Id.ToString()); + + Assert.IsTrue(obj.Name == newBrand.Name); + }); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id == brand.Id.ToString()); + + Assert.IsTrue(ret.Name == brand.Name); + + _trigger.Execute(); + + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == newBrand.Id.ToString() && ret.Name == newBrand.Name); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }).Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id.ToString()); + Assert.IsTrue(ret.Name == brand.Name); + _trigger.Execute(); + + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == newBrand.Id.ToString() && ret.Name == newBrand.Name); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id.ToString() && ret.Name == brand.Name); + } + + [TestMethod] + [DataRow("DccOptions.ManageServiceAddress", "http://localhost:6379")] + [DataRow("DccOptions.RedisOptions.DefaultDatabase", "0")] + [DataRow("DccOptions.RedisOptions.Password", "")] + public async Task GetDynamicAsync(string key, string value) + { + var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build(); + _services.AddSingleton(configuration); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var res = (await client.GetDynamicAsync(key)); + Assert.IsTrue(res + "" == value); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetDynamicAsyncByText(string environment, string cluster, string appId, string configObject) + { + string result = "Test"; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = result + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => + { + await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + }); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetDynamicAsyncByProperty(string environment, string cluster, string appId, string configObject) + { + var brand = new List() + { + new() + { + Key = "Id", + Value = Guid.NewGuid().ToString(), + }, + new() + { + Key = "Name", + Value = "Microsoft" + } + }; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Properties, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id == brand.Where(b => b.Key == "Id").Select(b => b.Value).FirstOrDefault()); + Assert.IsTrue(ret.Name == brand.Where(b => b.Key == "Name").Select(b => b.Value).FirstOrDefault()); + } +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs new file mode 100644 index 000000000..baecfe2bf --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs @@ -0,0 +1,162 @@ +using MASA.Contrib.BasicAbility.Dcc.Internal; +using MASA.Utils.Caller.Core; +using System.Net; + +namespace MASA.Contrib.BasicAbility.Dcc.Tests; + +[TestClass] +public class DccManageTest +{ + private DccSectionOptions _dccSectionOptions; + private JsonSerializerOptions _jsonSerializerOptions; + private Mock _callerProvider; + + [TestInitialize] + public void Initialize() + { + _dccSectionOptions = new DccSectionOptions() + { + Environment = "Test", + Cluster = "Default", + AppId = "DccTest", + ConfigObjects = new List() + { + "Test1" + }, + Secret = "Secret" + }; + _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + _callerProvider = new Mock(); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestUpdateAsync(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(brand.Serialize(_jsonSerializerOptions)) + }).Verifiable(); + + var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); + await manage.UpdateAsync(environment, cluster, appId, configObject, brand); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestUpdateAsyncAndError(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + + _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() + { + StatusCode = HttpStatusCode.ExpectationFailed, + Content = new StringContent("error") + }).Verifiable(); + + var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => await manage.UpdateAsync(environment, cluster, appId, configObject, brand)); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestUpdateAsyncAndCustomError(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() + { + StatusCode = (HttpStatusCode)299, + Content = new StringContent("custom error") + }).Verifiable(); + + var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => await manage.UpdateAsync(environment, cluster, appId, configObject, brand)); + } + + [DataTestMethod] + [DataRow("DccTest", "Secret")] + [DataRow("DccTest2", "Secret2")] + [DataRow("DccTest3", "")] + public void TestGetSecret(string appId, string secret) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, new List() + { + new() + { + Environment = "Test2", + Cluster = "Default2", + AppId = "DccTest2", + ConfigObjects = new List() + { + "Test12" + }, + Secret = "Secret2" + } + }); + if (string.IsNullOrEmpty(secret)) + Assert.ThrowsException(() => api.GetSecret(appId)); + else + Assert.IsTrue(api.GetSecret(appId) == secret); + } + + [DataTestMethod] + [DataRow("Test2", "Test2")] + [DataRow("", "Test")] + public void TestGetEnvironment(string environment, string outEnvironment) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + Assert.IsTrue(api.GetEnvironment(environment) == outEnvironment); + } + + [DataTestMethod] + [DataRow("CustomCluster", "CustomCluster")] + [DataRow("", "Default")] + public void GetCluster(string cluster, string outCluster) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + Assert.IsTrue(api.GetCluster(cluster) == outCluster); + } + + [DataTestMethod] + [DataRow("CustomAppid", "CustomAppid")] + [DataRow("", "DccTest")] + public void GetAppid(string appId, string outAppid) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + Assert.IsTrue(api.GetAppId(appId) == outAppid); + } + + [DataTestMethod] + [DataRow("configObject", "configObject")] + [DataRow("", "")] + public void GetConfigObject(string configObject, string outConfigObject) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + if (string.IsNullOrEmpty(configObject)) + Assert.ThrowsException(() => api.GetConfigObject(configObject)); + else + Assert.IsTrue(api.GetConfigObject(configObject) == outConfigObject); + } +} + +public class CustomConfigurationAPI : ConfigurationAPIBase +{ + public CustomConfigurationAPI(DccSectionOptions defaultSectionOption, List? expandSectionOptions) : base(defaultSectionOption, expandSectionOptions) + { + } + + public new string GetSecret(string appId) => base.GetSecret(appId); + + public new string GetEnvironment(string environment) => base.GetEnvironment(environment); + + public new string GetCluster(string cluster) => base.GetCluster(cluster); + + public new string GetAppId(string appId) => base.GetAppId(appId); + + public new string GetConfigObject(string configObject) => base.GetConfigObject(configObject); +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs new file mode 100644 index 000000000..8d5394a69 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs @@ -0,0 +1,799 @@ +using MASA.Utils.Caching.Core.Interfaces; +using MASA.Utils.Caching.Core.Models; +using MASA.Utils.Caching.DistributedMemory.Models; +using MASA.Utils.Caller.Core; +using MASA.Utils.Caller.HttpClient; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace MASA.Contrib.BasicAbility.Dcc.Tests; + +[TestClass] +public class DccTest +{ + private string DEFAULT_CLIENT_NAME = "masa.plugins.caching.dcc"; + private Mock _masaConfigurationBuilder; + private JsonSerializerOptions _jsonSerializerOptions; + private IServiceCollection _services; + + private Mock _memoryCacheClientFactory; + private Mock _memoryCache; + private Mock _distributedCacheClient; + private const string DefaultEnvironmentName = "ASPNETCORE_ENVIRONMENT"; + private const string DEFAULT_SUBSCRIBE_KEY_PREFIX = "masa.dcc:"; + + [TestInitialize] + public void Initialize() + { + _masaConfigurationBuilder = new Mock(); + _memoryCacheClientFactory = new Mock(); + _memoryCache = new Mock(); + _distributedCacheClient = new Mock(); + _services = new ServiceCollection(); + _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + } + + [TestMethod] + public void TestErrorDccSection() + { + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary()).Verifiable(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(new ServiceCollection())); + } + + [TestMethod] + public void TestTryAddConfigurationApiClient() + { + _memoryCacheClientFactory.Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)).Returns(() => null!).Verifiable(); + _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), null!); + Assert.IsTrue(_services.Count(service => service.ServiceType == typeof(IConfigurationApiClient) && service.Lifetime == ServiceLifetime.Singleton) == 1); + Assert.ThrowsException(() => + { + var clienties = _services.BuildServiceProvider().GetServices(); + }); + + _services = new ServiceCollection(); + _memoryCacheClientFactory + .Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)) + .Returns(() => new MemoryCacheClient(_memoryCache.Object, _distributedCacheClient.Object, SubscribeKeyTypes.ValueTypeFullNameAndKey)) + .Verifiable(); + _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), new JsonSerializerOptions() + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + + var clienties = _services.BuildServiceProvider().GetServices(); + Assert.IsTrue(clienties.Count() == 1); + + _services = new ServiceCollection(); + _memoryCacheClientFactory + .Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)) + .Returns(() => new MemoryCacheClient(_memoryCache.Object, _distributedCacheClient.Object, Utils.Caching.Core.Models.SubscribeKeyTypes.ValueTypeFullNameAndKey)) + .Verifiable(); + _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), _jsonSerializerOptions); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), _jsonSerializerOptions); + clienties = _services.BuildServiceProvider().GetServices(); + Assert.IsTrue(clienties.Count() == 1); + } + + [TestMethod] + public void TestTryAddConfigurationApiManage() + { + Mock httpClientFactory = new(); + _services.AddSingleton(httpClientFactory.Object); + _services.AddCaller(options => options.UseHttpClient()); + + MasaConfigurationExtensions.TryAddConfigurationApiManage(_services, new DccSectionOptions(), new List()); + MasaConfigurationExtensions.TryAddConfigurationApiManage(_services, new DccSectionOptions(), new List()); + Assert.IsTrue(_services.Count(service => service.ServiceType == typeof(IConfigurationApiManage) && service.Lifetime == ServiceLifetime.Singleton) == 1); + var serviceProvider = _services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseDCCAndErrorSection() + { + _services.AddCaller(options => options.UseHttpClient()); + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary()).Verifiable(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, "", null, null), "configureOptions"); + } + + [TestMethod] + public void TestUseDCCAndNullDccConfigurationOption() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => null!, option => + { + option.AppId = "Test"; + option.Environment = "Test"; + option.ConfigObjects = new List() { "Te" }; + }, null), "configureOptions"); + } + + [TestMethod] + public void TestCustomCaller() + { + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = string.Empty, + ConfigFormat = ConfigFormats.Text + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + _masaConfigurationBuilder.Object.UseDcc(_services, () => new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }, option => + { + option.AppId = "Test"; + option.Environment = "Test"; + option.ConfigObjects = new List() + { + "Settings" + }; + }, null, jsonSerializerOption => + { + jsonSerializerOption.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + }, option => + { + option.UseHttpClient(builder => + { + builder.Name = "CustomHttpClient"; + builder.Configure = opt => opt.BaseAddress = new Uri("https://github.com"); + }); + }); + var callerProvider = _services.BuildServiceProvider().GetRequiredService().CreateClient("CustomHttpClient"); + Assert.IsNotNull(callerProvider); + } + + [TestMethod] + public void TestUseDCCAndEmptyDccServiceAddress() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "", + }; + }, null!, null), "DccServiceAddress"); + } + + [TestMethod] + public void TestUseDCCAndErrorDccService() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = null! + } + }; + }, null!, null), "Servers"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions + { + Servers = new List() + } + }; + }, null!, null), "Servers"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions + { + Servers = new List() + { + new() + { + Host="", + Port=8080 + } + } + } + }; + }, null!, null), "Servers"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host="localhost", + Port=-1 + } + } + } + }; + }, null!, null), "Servers"); + } + + [TestMethod] + public void TestUseDCCAndErrorDefaultSectionOption() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, null!, null), "defaultSectionOptions"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = ""; + }, null), "AppId cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = null!; + }, null), "ConfigObjects cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List(); + }, null), "ConfigObjects cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, null), "Error getting environment information, please make sure the value of ASPNETCORE_ENVIRONMENT has been configured"); + } + + [TestMethod] + public void TestUseDCCAndErrorExpansionSectionOptions() + { + System.Environment.SetEnvironmentVariable(DefaultEnvironmentName, "Test"); + + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test2", + } + }; + }), "ConfigObjects in the extension section cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test2", + ConfigObjects=new List() + } + }; + }), "ConfigObjects in the extension section cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test", + ConfigObjects=new List() + { + "Settings" + } + } + }; + }), "The current section already exists, no need to mount repeatedly"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test2", + ConfigObjects=new List() + { + "Settings" + } + }, + new() + { + AppId = "Test2", + ConfigObjects=new List() + { + "Settings" + } + } + }; + }), "The current section already exists, no need to mount repeatedly"); + } + + [DataTestMethod] + [DataRow("Development", "Default", "WebApplication1", "Brand")] + public void TestUseDCCAndSuccess(string environment, string cluster, string appId, string configObject) + { + System.Environment.SetEnvironmentVariable(DefaultEnvironmentName, "Test"); + var brand = new Brands("Microsoft"); + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = System.Text.Json.JsonSerializer.Serialize(brand), + ConfigFormat = ConfigFormats.Json + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, null); + var optionFactory = _services.BuildServiceProvider().GetRequiredService>(); + var option = optionFactory.Create(DEFAULT_CLIENT_NAME); + + Assert.IsTrue(option.SubscribeKeyType == SubscribeKeyTypes.SpecificPrefix); + + Assert.IsTrue(option.SubscribeKeyPrefix == DEFAULT_SUBSCRIBE_KEY_PREFIX); + } + + [DataTestMethod] + [DataRow("Development", "Default", "WebApplication1", "Brand")] + public void TestUseDccAndSingleSection(string environment, string cluster, string appId, string configObject) + { + CustomTrigger trigger = new CustomTrigger(_jsonSerializerOptions); + var brand = new Brands("Microsoft"); + var newBrand = new Brands("Masa"); + + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = brand.Serialize(_jsonSerializerOptions), + ConfigFormat = ConfigFormats.Text + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue( + configurationApiClient + .GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) + .GetAwaiter() + .GetResult().Raw == brand.Serialize(_jsonSerializerOptions)); + trigger.Execute(); + + Initialize(); + + Dictionary masaDic = new Dictionary() + { + { "Id", Guid.NewGuid().ToString() }, + { "Name", "Masa" } + }; + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = masaDic.Serialize(_jsonSerializerOptions), + ConfigFormat = ConfigFormats.Json + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).Result.Raw == masaDic.Serialize(_jsonSerializerOptions)); + + Initialize(); + + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = "Test", + ConfigFormat = ConfigFormats.Text + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).GetAwaiter().GetResult().Raw == "Test"); + + Initialize(); + + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = null, + ConfigFormat = ConfigFormats.Text + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).GetAwaiter().GetResult().Raw == null); + + Initialize(); + + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = "Test", + ConfigFormat = (ConfigFormats)4 + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services)); + } + + [TestMethod] + public void TestUseDccAndExpandSections() + { + var brand = new Brands("Microsoft"); + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = JsonSerializer.Serialize(brand), + ConfigFormat = ConfigFormats.Json + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("expandSections.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + + var result = configurationApiClient.GetRawAsync("Test", "Default", "DccTest", "Test1", It.IsAny>()) + .ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.IsTrue(result.Raw == JsonSerializer.Serialize(brand)); + } + + [DataTestMethod] + [DataRow("Development", "Default", "WebApplication1", "Brand")] + public void TestUseMultiDcc(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = JsonSerializer.Serialize(brand), + ConfigFormat = ConfigFormats.Json + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services).UseDcc(_services); + var result = configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) + .ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.IsTrue(result.Raw == JsonSerializer.Serialize(brand)); + + var httpClient = _services.BuildServiceProvider().GetRequiredService().CreateClient(DEFAULT_CLIENT_NAME); + Assert.IsTrue(httpClient.BaseAddress!.ToString() == "http://localhost:6379/"); + } + +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs new file mode 100644 index 000000000..7f5827b59 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs @@ -0,0 +1,7 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Common; + +internal static class SerializeCommon +{ + public static string Serialize(this object obj, JsonSerializerOptions? jsonSerializerOptions) + => JsonSerializer.Serialize(obj, jsonSerializerOptions); +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs new file mode 100644 index 000000000..01735427f --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Config; + +internal class Property +{ + public string Key { get; set; } = default!; + + public string Value { get; set; } = default!; +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs new file mode 100644 index 000000000..5cafa5fe5 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal; + +internal class PublishRelease +{ + public ConfigFormats ConfigFormat { get; set; } + + public string? Content { get; set; } +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs new file mode 100644 index 000000000..93088edcc --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs @@ -0,0 +1,26 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal; + +public class CustomTrigger +{ + private JsonSerializerOptions _jsonSerializerOptions; + + public CustomTrigger(JsonSerializerOptions jsonSerializerOptions) + { + _jsonSerializerOptions = jsonSerializerOptions; + } + + internal ConfigFormats Formats { get; set; } + + internal string Content { get; set; } + + internal Action Action { get; set; } + + internal void Execute() + { + Action?.Invoke(new PublishRelease() + { + ConfigFormat = Formats, + Content = Content + }.Serialize(_jsonSerializerOptions)); + } +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs new file mode 100644 index 000000000..15e6e5d3b --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; + +internal enum ConfigFormats +{ + Properties = 1, + Text, + Json +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs new file mode 100644 index 000000000..d394e9e06 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs @@ -0,0 +1,14 @@ +namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Model; + +internal class Brands +{ + public Guid Id { get; init; } + + public string Name { get; set; } + + public Brands() + => this.Id = Guid.NewGuid(); + + public Brands(string Name) : this() + => this.Name = Name; +} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj b/test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj new file mode 100644 index 000000000..8d6f3f437 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + enable + false + enable + + + + + Always + + + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs new file mode 100644 index 000000000..8161f76a2 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs @@ -0,0 +1,14 @@ +global using MASA.BuildingBlocks.Configuration; +global using MASA.Contrib.BasicAbility.Dcc.Options; +global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal; +global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Common; +global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Config; +global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; +global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Model; +global using MASA.Utils.Caching.DistributedMemory; +global using MASA.Utils.Caching.DistributedMemory.Interfaces; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System.Text.Json; diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json b/test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json new file mode 100644 index 000000000..144652eea --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json @@ -0,0 +1,23 @@ +{ + "DccOptions": { + "ManageServiceAddress": "http://localhost:6379", + "SubscribeKeyPrefix": "masa.dcc:", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8888 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "WebApplication1", + "Environment": "Development", + "Cluster": "Default", + "ConfigObjects": [ + "Brand" + ], + "Sectet": "" +} \ No newline at end of file diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json b/test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json new file mode 100644 index 000000000..28e953152 --- /dev/null +++ b/test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json @@ -0,0 +1,32 @@ +{ + "DccOptions": { + "ManageServiceAddress": "http://localhost:6379", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8888 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "DccTest", + "Environment": "Test", + "Cluster": "Default", + "ConfigObjects": [ + "Test1" + ], + "Sectet": "", + "ExpandSections": [ + { + "AppId": "DccTest2", + "Environment": "Test2", + "Cluster": "Default", + "ConfigObjects": [ + "Test3" + ] + } + ] +} \ No newline at end of file diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs new file mode 100644 index 000000000..1ab0da584 --- /dev/null +++ b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs @@ -0,0 +1,12 @@ +namespace MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests; + +public class ErrorKafkaOptions : KafkaOptions +{ + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + public ErrorKafkaOptions() + { + base.Section = "KafkaOptions"; + } +} diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs new file mode 100644 index 000000000..a2f1f13ee --- /dev/null +++ b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs @@ -0,0 +1,15 @@ +namespace MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests; + +public class KafkaOptions : MasaConfigurationOptions +{ + public string Servers { get; set; } + + public int ConnectionPoolSize { get; set; } + + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + public KafkaOptions() + { + base.ParentSection = ""; + } +} diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj new file mode 100644 index 000000000..0ddf5d6a8 --- /dev/null +++ b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs new file mode 100644 index 000000000..ddfd0182f --- /dev/null +++ b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using MASA.BuildingBlocks.Configuration; +global using System.Text.Json.Serialization; diff --git a/test/MASA.Contribs.DDD.Domain.Repository/MASA.Contribs.DDD.Domain.Repository.csproj b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj similarity index 61% rename from test/MASA.Contribs.DDD.Domain.Repository/MASA.Contribs.DDD.Domain.Repository.csproj rename to test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj index 268cbace4..11cf31006 100644 --- a/test/MASA.Contribs.DDD.Domain.Repository/MASA.Contribs.DDD.Domain.Repository.csproj +++ b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj @@ -2,9 +2,13 @@ net6.0 - enable enable false + enable + + + + diff --git a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs new file mode 100644 index 000000000..22de5854b --- /dev/null +++ b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs @@ -0,0 +1,12 @@ +๏ปฟnamespace MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests; + +public class MountSectionRedisOptions : MasaConfigurationOptions +{ + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = null; + + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; +} diff --git a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs new file mode 100644 index 000000000..ddfd0182f --- /dev/null +++ b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using MASA.BuildingBlocks.Configuration; +global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs b/test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs new file mode 100644 index 000000000..3bbcc3a4d --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs @@ -0,0 +1,16 @@ +namespace MASA.Contrib.Configuration.Tests.Config; + +public class RabbitMqOptions : MasaConfigurationOptions +{ + public string HostName { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public string VirtualHost { get; set; } + + public string Port { get; set; } + + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; +} diff --git a/test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs b/test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs new file mode 100644 index 000000000..3fe96cb56 --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs @@ -0,0 +1,10 @@ +namespace MASA.Contrib.Configuration.Tests.Config; + +public class RedisOptions +{ + public string Ip { get; set; } + + public string Password { get; set; } + + public int Port { get; set; } +} diff --git a/test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs b/test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs new file mode 100644 index 000000000..39f32fa84 --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs @@ -0,0 +1,14 @@ +namespace MASA.Contrib.Configuration.Tests.Config; + +public class SystemOptions : MasaConfigurationOptions +{ + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = null; + + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + public string? Name { get; set; } +} diff --git a/test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs b/test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs new file mode 100644 index 000000000..fe62c475c --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs @@ -0,0 +1,266 @@ +namespace MASA.Contrib.Configuration.Tests; + +[TestClass] +public class ConfigurationTest +{ + private IConfigurationBuilder _configurationBuilder; + + [TestInitialize] + public void Initialize() + { + _configurationBuilder = new ConfigurationBuilder(); + } + + [TestMethod] + public void TestAddSection() + { + var masaConfigurationBuilder = new MasaConfigurationBuilder(_configurationBuilder); + Assert.ThrowsException(() => masaConfigurationBuilder.AddSection(null!)); + + Assert.ThrowsException(() => masaConfigurationBuilder.AddSection(new ConfigurationBuilder())); + + masaConfigurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true), "appsettings" + ); + + Assert.IsTrue(masaConfigurationBuilder.GetSectionRelations().Count == 1); + + masaConfigurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("redis.json", true, true) + ); + Assert.IsTrue(masaConfigurationBuilder.GetSectionRelations().Count == 2); + + Assert.ThrowsException(() => + { + masaConfigurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("rabbitMq.json", true, true) + ); + }); + } + + [TestMethod] + public void TestAddCustomSection() + { + var builder = WebApplication.CreateBuilder(); + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true), "RedisOptions"); + + configurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + + configurationBuilder.UseMasaOptions(option => + { + option.Mapping(SectionTypes.Local, ""); + }); + }); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var redisOption = serviceProvider.GetRequiredService>(); + + Assert.IsNotNull(configuration); + Assert.IsNotNull(redisOption); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + } + + [TestMethod] + public void TestAddMasaConfiguration() + { + var builder = WebApplication.CreateBuilder(); + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + option.Mapping(SectionTypes.Local, "", "") + ); + }); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var redisOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:Ip"] == "localhost"); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + + var rabbitMqOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:RabbitMqOptions:UserName"] == "admin"); + Assert.IsTrue(rabbitMqOption.Value.UserName == "admin" && rabbitMqOption.Value.Password == "admin"); + } + + [TestMethod] + public void TestAddMultiMasaConfiguration() + { + var builder = WebApplication.CreateBuilder(); + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + option.Mapping(SectionTypes.Local, "", "") + ); + }).AddMasaConfiguration(); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var redisOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:Ip"] == "localhost"); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + + var rabbitMqOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:RabbitMqOptions:UserName"] == "admin"); + Assert.IsTrue(rabbitMqOption.Value.UserName == "admin" && rabbitMqOption.Value.Password == "admin"); + } + + [TestMethod] + public void TestAutoMapSectionError() + { + var builder = WebApplication.CreateBuilder(); + builder.Host.ConfigureAppConfiguration((context, config) => { config.Sources.Clear(); }); + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true); + builder.Configuration.AddConfiguration(chainedConfiguration.Build()); + + Assert.ThrowsException(() => + builder.AddMasaConfiguration(configurationBuilder => + { + }, "Appsettings", typeof(ConfigurationTest).Assembly, typeof(KafkaOptions).Assembly)); + } + + [TestMethod] + public void TestAutoMapAndErrorSection() + { + var builder = WebApplication.CreateBuilder(); + Assert.ThrowsException(() => + { + return builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); //Mount to the Local section + }, "Appsettings", typeof(ConfigurationTest).Assembly, typeof(MountSectionRedisOptions).Assembly); + }); + } + + [TestMethod] + public void TestRepeatMappting() + { + var builder = WebApplication.CreateBuilder(); + Assert.ThrowsException(() => + { + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + { + option.Mapping(SectionTypes.Local, "", ""); + option.Mapping(SectionTypes.Local, "", ""); + }); + }); + }); + } + + [TestMethod] + public void TestCreateMasaConfiguration() + { + var services = new ServiceCollection(); + services.CreateMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + option.Mapping(SectionTypes.Local, "", "") + ); + }, new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true), "Appsettings"); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var redisOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + } + + [TestMethod] + public void TestNullSection() + { + var services = new ServiceCollection(); + var ex = Assert.ThrowsException(() => services.CreateMasaConfiguration(null)); + Assert.IsTrue(ex.Message == "Please add the section to be loaded"); + } + + [TestMethod] + public void TestConfigurationChange() + { + var builder = WebApplication.CreateBuilder(); + + var rootPath = builder.Environment.ContentRootPath; + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(rootPath) + .AddJsonFile("redis.json", true, true), "RedisOptions"); + + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(rootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + + configurationBuilder.UseMasaOptions(option => + { + option.Mapping(SectionTypes.Local, ""); + }); + }, "Appsettings", typeof(ConfigurationTest).Assembly); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var systemOption = serviceProvider.GetRequiredService>(); + + Assert.IsNotNull(configuration); + Assert.IsNotNull(systemOption); + Assert.IsTrue(systemOption.Value.Name == "MASA TEST"); + + var newRedisOption = systemOption.Value; + newRedisOption.Name = null; + + File.WriteAllText(Path.Combine(rootPath, "appsettings.json"), System.Text.Json.JsonSerializer.Serialize(new { SystemOptions = newRedisOption })); + + Thread.Sleep(2000); + var option = serviceProvider.GetRequiredService>(); + Assert.IsTrue(option.CurrentValue.Name == ""); + } +} diff --git a/test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj b/test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj new file mode 100644 index 000000000..318fc49f0 --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj @@ -0,0 +1,43 @@ + + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + Always + + + + Always + + + + Always + + + + diff --git a/test/MASA.Contrib.Configuration.Tests/_Imports.cs b/test/MASA.Contrib.Configuration.Tests/_Imports.cs new file mode 100644 index 000000000..12d9cc8ba --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/_Imports.cs @@ -0,0 +1,10 @@ +global using MASA.BuildingBlocks.Configuration; +global using MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests; +global using MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests; +global using MASA.Contrib.Configuration.Tests.Config; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.Configuration.Tests/appsettings.json b/test/MASA.Contrib.Configuration.Tests/appsettings.json new file mode 100644 index 000000000..9c1279ff5 --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/appsettings.json @@ -0,0 +1,9 @@ +{ + "KafkaOptions": { + "Servers": "Kafka Server", + "int": 10 + }, + "SystemOptions": { + "Name": "MASA TEST" + } +} \ No newline at end of file diff --git a/test/MASA.Contrib.Configuration.Tests/rabbitMq.json b/test/MASA.Contrib.Configuration.Tests/rabbitMq.json new file mode 100644 index 000000000..cbff2c19a --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/rabbitMq.json @@ -0,0 +1,7 @@ +{ + "HostName": "localhost", + "UserName": "admin", + "Password": "admin", + "VirtualHost": "/", + "Port": 5672 +} \ No newline at end of file diff --git a/test/MASA.Contrib.Configuration.Tests/redis.json b/test/MASA.Contrib.Configuration.Tests/redis.json new file mode 100644 index 000000000..ebce8f626 --- /dev/null +++ b/test/MASA.Contrib.Configuration.Tests/redis.json @@ -0,0 +1,5 @@ +{ + "Ip": "localhost", + "Password": "", + "Port": 6379 +} \ No newline at end of file diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj similarity index 91% rename from test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.csproj rename to test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj index 1f9d41356..9dc08646c 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.csproj +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj @@ -2,8 +2,9 @@ net6.0 - enable enable + false + enable diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs new file mode 100644 index 000000000..d9363e62d --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs @@ -0,0 +1,28 @@ +namespace MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests; + +public class Students : AggregateRoot +{ + public Students() + { + RegisterTime = DateTime.UtcNow; + } + + public string SerialNumber { get; set; } = default!; + + public string Name { get; set; } + + public int Age { get; set; } + + public DateTime RegisterTime { get; private set; } + + /// + /// Test the case of the joint primary key error, no business value + /// + /// + public override IEnumerable<(string Name, object Value)> GetKeys() + => new List<(string Name, object Value)>() + { + ("SerialNumber", SerialNumber), + ("","") + }; +} diff --git a/test/MASA.Contribs.DDD.Domain.Entities/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs similarity index 100% rename from test/MASA.Contribs.DDD.Domain.Entities/_Imports.cs rename to test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs new file mode 100644 index 000000000..45965f525 --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs @@ -0,0 +1,19 @@ +namespace MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests; + +public class Courses : AggregateRoot +{ + public Courses() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; init; } + + public string Name { get; set; } + + public override IEnumerable<(string Name, object Value)> GetKeys() + => new List<(string Name, object Value)>() + { + ("Names",Name)//Demonstrate that a non-existent key is used as a joint primary key + }; +} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj new file mode 100644 index 000000000..9dc08646c --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs new file mode 100644 index 000000000..d6a2a9e7d --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs @@ -0,0 +1 @@ +global using MASA.BuildingBlocks.DDD.Domain.Entities; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/Entities/User.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs similarity index 91% rename from test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/Entities/User.cs rename to test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs index d4f5399ee..915381728 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/Entities/User.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs @@ -1,4 +1,4 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Entities; +namespace MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Entities; public class User : AggregateRoot { diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj new file mode 100644 index 000000000..453ed369c --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/Repositories/IUserRepository.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs similarity index 55% rename from test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/Repositories/IUserRepository.cs rename to test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs index ecdd10afe..83e3677da 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/Repositories/IUserRepository.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs @@ -1,4 +1,6 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Repositories; +using MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Entities; + +namespace MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Repositories; public interface IUserRepository : IRepository { diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs similarity index 58% rename from test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/_Imports.cs rename to test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs index 2348cb021..5db641dcf 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository/_Imports.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs @@ -1,3 +1,2 @@ global using MASA.BuildingBlocks.DDD.Domain.Entities; global using MASA.BuildingBlocks.DDD.Domain.Repositories; -global using MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Entities; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs new file mode 100644 index 000000000..f95e10bc4 --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs @@ -0,0 +1,16 @@ +namespace MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests; + +public class Hobbies : AggregateRoot +{ + public string Name { get; private set; } + + private Hobbies() + { + Id = Guid.NewGuid(); + } + + public Hobbies(string name) : this() + { + Name = name; + } +} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj new file mode 100644 index 000000000..9f97c3017 --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj @@ -0,0 +1,14 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs new file mode 100644 index 000000000..d6a2a9e7d --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs @@ -0,0 +1 @@ +global using MASA.BuildingBlocks.DDD.Domain.Entities; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs new file mode 100644 index 000000000..1778f8154 --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs @@ -0,0 +1,78 @@ +namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests; + +[TestClass] +public class BaseRepositoryTest : TestBase +{ + private IServiceCollection _services = default!; + private Assembly[] _assemblies; + private Mock _uoW; + private Mock _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + _assemblies = new Assembly[1] + { + typeof(BaseRepositoryTest).Assembly + }; + _uoW = new(); + _dispatcherOptions = new(); + _dispatcherOptions.Setup(options => options.Services).Returns(() => _services); + } + + [TestMethod] + public void TestNullServices() + { + Assert.ThrowsException(() => + { + _dispatcherOptions.Setup(options => options.Services).Returns(() => null!); + var options = _dispatcherOptions.Object.UseRepository(); + }); + } + + [TestMethod] + public void TestUseCustomRepositoryAndNotImplementation() + { + Mock uoW = new(); + _services.AddScoped(serviceProvider => uoW.Object); + + Assert.ThrowsException(() + => _dispatcherOptions.Object.UseRepository(typeof(TestBase).Assembly, typeof(IUserRepository).Assembly) + ); + } + + [TestMethod] + public void TestNullUnitOfWork() + { + var ex = Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(_assemblies); + }); + Assert.IsTrue(ex.Message == "Please add UoW first."); + } + + [TestMethod] + public void TestNullAssembly() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + + Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(null!); + }); + } + + [TestMethod] + public void TestAddMultRepository() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies).UseRepository(); + + var serviceProvider = _services.BuildServiceProvider(); + var repository = serviceProvider.GetServices>(); + Assert.IsTrue(repository.Count() == 1); + } +} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs index b40622c72..27f677f52 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs @@ -1,24 +1,23 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities +namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; + +public class Address : ValueObject { - public class Address : ValueObject - { - public string Street { get; set; } + public string Street { get; set; } - public string City { get; set; } + public string City { get; set; } - public string State { get; set; } + public string State { get; set; } - public string Country { get; set; } + public string Country { get; set; } - public string ZipCode { get; set; } + public string ZipCode { get; set; } - protected override IEnumerable GetEqualityValues() - { - yield return Street; - yield return City; - yield return State; - yield return Country; - yield return ZipCode; - } + protected override IEnumerable GetEqualityValues() + { + yield return Street; + yield return City; + yield return State; + yield return Country; + yield return ZipCode; } } diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs index 0ed2dada2..09fed6f3c 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs @@ -1,19 +1,40 @@ namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; -public class Orders : AuditAggregateRoot +public class Orders : AuditAggregateRoot { public int OrderNumber { get; set; } - public DateTime OrderDate { get; set; } + public DateTime OrderDate { get; private set; } - public string OrderStatus { get; set; } + public string OrderStatus { get; private set; } - public string Description { get; set; } - - public string BuyerId { get; set; } - - public string BuyerName { get; set; } + public string Description { get; set; } = default!; public List OrderItems { get; set; } + + public Orders() + { + this.OrderDate = DateTime.UtcNow; + this.OrderItems = new(); + this.OrderStatus = "Submitted"; + } + + public Orders(int id) : this() + { + base.Id = id; + } + + /// + /// Joint primary key, when this method does not exist, the primary key is Id + /// + /// + public override IEnumerable<(string Name, object Value)> GetKeys() + { + return new List<(string Name, object value)> + { + ("Id", Id), + ("OrderNumber", OrderNumber) + }; + } } diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs index 39f509ad2..03d6adba4 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs @@ -2,4 +2,5 @@ namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Repositories; public interface IOrderRepository : IRepository { + Task AddAsync(Orders order); } diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs new file mode 100644 index 000000000..31e12e8cd --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs @@ -0,0 +1,23 @@ +namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure; + +public class CustomDbContext : DbContext +{ + public DbSet Orders { get; set; } + + public DbSet Students { get; set; } + + public DbSet Courses { get; set; } + + public DbSet Hobbies { get; set; } + + public CustomDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + entityTypeBuilder => + { + entityTypeBuilder.HasKey("SerialNumber"); + }); + } +} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Options/DispatcherOptions.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Options/DispatcherOptions.cs deleted file mode 100644 index 1aadc3d55..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Options/DispatcherOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure.Options; - -public class DispatcherOptions : IDispatcherOptions -{ - public IServiceCollection Services { get; } - - public DispatcherOptions(IServiceCollection services) => Services = services; -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/OrderDbContext.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/OrderDbContext.cs deleted file mode 100644 index 09ca126e4..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/OrderDbContext.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure; - -public class OrderDbContext : DbContext -{ - public OrderDbContext(DbContextOptions options) : base(options) { } - - public DbSet Orders { get; set; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs index 498158cc6..717b8527f 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs @@ -1,8 +1,23 @@ namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure.Repositories; -public class OrderRepository : Repository, IOrderRepository +public class OrderRepository : Repository, IOrderRepository { - public OrderRepository(OrderDbContext context, IUnitOfWork unitOfWork) : base(context, unitOfWork) + public OrderRepository(CustomDbContext context, IUnitOfWork unitOfWork) : base(context, unitOfWork) { } + + public async Task AddAsync(Orders order) + { + try + { + var transaction = base.Transaction; + await base.AddAsync(order, default); + await base.SaveChangesAsync(); + await base.CommitAsync(); + } + catch (Exception) + { + await base.RollbackAsync(); + } + } } diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj index 2ff0d55c2..9f271c9aa 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj @@ -3,22 +3,29 @@ net6.0 enable - false + enable + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + - + + + + diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs index 5e431db8f..dba5d0c7e 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs @@ -3,80 +3,271 @@ namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests; [TestClass] public class RepositoryTest : TestBase { - private readonly Assembly[] _assemblies; + private IServiceCollection _services = default!; + private Assembly[] _assemblies; + private Mock _uoW; + private Mock _dispatcherOptions = default!; - public RepositoryTest() + [TestInitialize] + public void Initialize() { + _services = new ServiceCollection(); _assemblies = new Assembly[1] { - typeof(RepositoryTest).Assembly + typeof(BaseRepositoryTest).Assembly }; + _uoW = new(); + _uoW.Setup(uoW => uoW.UseTransaction).Returns(true); + _dispatcherOptions = new(); + _dispatcherOptions.Setup(options => options.Services).Returns(() => _services); + } [TestMethod] - public void TestNoServices() + public async Task TestAsync() { - Assert.ThrowsException(() => + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + dbContext.SaveChanges(); + }); + _uoW.Setup(u => u.CommitAsync(default)).Verifiable(); + var orders = new List() + { + new Orders(1) + { + OrderNumber = 9999999, + Description = "Apple", + }, + new Orders(2) + { + OrderNumber = 9999999, + Description = "Apple2", + } + }; + + var repository = serviceProvider.GetRequiredService>(); + await repository.AddRangeAsync(orders); + await repository.UnitOfWork.SaveChangesAsync(); + + var orderList = await repository.GetListAsync(order => order.OrderNumber == 9999999, default); + Assert.IsNotNull(orderList); + Assert.IsTrue(orderList.Count() == 2); + + Assert.IsTrue((await repository.GetListAsync(order => order.Description == "Apple", default)).Count() == 1); + Assert.IsTrue(await repository.GetCountAsync(order => order.Description == "Apple", default) == 1); + + var huaweiOrder = await repository.FindAsync(order => order.Description == "Apple2"); + huaweiOrder!.Description = "HuaWei"; + huaweiOrder.OrderNumber = 9999998; + await repository.UnitOfWork.SaveChangesAsync(default); + + Assert.IsTrue((await repository.GetListAsync(order => order.Description == "Apple", default)).Count() == 1); + Assert.IsTrue(await repository.GetCountAsync(order => order.Description == "HuaWei", default) == 1); + + await repository.AddAsync(new Orders(3) + { + OrderNumber = 9999997, + Description = "Google" + }); + await repository.AddAsync(new Orders(4) { - var options = new DispatcherOptions(null).UseRepository(); + OrderNumber = 9999996, + Description = "Microsoft" }); + + await repository.RemoveAsync(order => order.Description == "Apple", default); + await repository.UnitOfWork.SaveChangesAsync(default); + + var list = await repository.GetPaginatedListAsync(0, 10, null, default); + + Assert.IsTrue(list.Count == 3); + Assert.IsTrue(list[0].Description == "HuaWei"); + Assert.IsTrue(list[1].Description == "Google"); + Assert.IsTrue(list[2].Description == "Microsoft"); + + list = await repository.GetPaginatedListAsync(1, 10, null, default); + Assert.IsTrue(list.Count == 2); + Assert.IsTrue(list[0].Description == "Google"); + Assert.IsTrue(list[1].Description == "Microsoft"); + + list = await repository.GetPaginatedListAsync(order => order.Description != "Google", 0, 10, null, default); + Assert.IsTrue(list.Count == 2); + Assert.IsTrue(list[0].Description == "HuaWei"); + + var count = await repository.GetCountAsync(default); + Assert.IsTrue(count == 3); + + var huaWei = await repository.FindAsync(huaweiOrder.Id, huaweiOrder.OrderNumber); + await repository.RemoveAsync(huaWei!, default); + + await repository.UnitOfWork.SaveChangesAsync(default); + Assert.IsTrue(await repository.GetCountAsync(default) == 2); + + var remainingOrders = await repository.GetListAsync(default); + await repository.RemoveRangeAsync(remainingOrders); + await repository.UnitOfWork.SaveChangesAsync(default); + + Assert.IsTrue(await repository.GetCountAsync(default) == 0); } [TestMethod] - public void TestUseCustomRepositoryAndNotImplementation() + public async Task TestTranscationFailedAsync() { - var services = new ServiceCollection(); + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies); - Mock uow = new(); - services.AddScoped(serviceProvider => uow.Object); + var serviceProvider = _services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + dbContext.Database.BeginTransaction(); - Assert.ThrowsException(() => new DispatcherOptions(services).UseRepository(typeof(TestBase).Assembly, typeof(IUserRepository).Assembly)); + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + dbContext.SaveChanges(); + }); + _uoW.Setup(u => u.CommitAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.Commit(); + }); + _uoW.Setup(u => u.RollbackAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.RollbackAsync(); + }); + var repository = serviceProvider.GetRequiredService(); + + var order = new Orders() + { + OrderNumber = 1, + }; + await repository.AddAsync(order); + Assert.IsTrue(await repository.GetCountAsync(default) == 0); } [TestMethod] - public void TestNoUnitOfWorkAssembly() + public async Task TestTranscationSucceededAsync() { - Assert.ThrowsException(() => + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + dbContext.SaveChanges(); + }); + _uoW.Setup(u => u.CommitAsync(default)).Callback(() => { - var serviceProvider = base.CreateServiceProvider(null, _assemblies); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.Commit(); }); + _uoW.Setup(u => u.RollbackAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.RollbackAsync(); + }); + var repository = serviceProvider.GetRequiredService(); + + var order = new Orders(1) + { + OrderNumber = 1, + Description = "Apple" + }; + await repository.AddAsync(order); + Assert.IsTrue(await repository.GetCountAsync(default) == 1); } [TestMethod] - public void TestNullAssembly() + public async Task TestUpdateAsync() { - var serviceProvider = base.CreateDefaultServiceProvider(null)!; - var repository = serviceProvider.GetRequiredService>(); - Assert.IsNotNull(repository); - repository.AddAsync(new Orders() + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => { - BuyerName = "lisa" + dbContext.SaveChanges(); }); + var repository = serviceProvider.GetRequiredService(); + + var order = new Orders(1) + { + OrderNumber = 1, + Description = "Apple" + }; + await repository.AddAsync(order, default); + await repository.UnitOfWork.SaveChangesAsync(default); + dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; + + order = await repository.FindAsync(order => order.Description == "Apple"); + order!.Description = "Apple Company"; + await repository.UnitOfWork.SaveChangesAsync(); + + order = await repository.FindAsync(order => order.Description == "Apple"); + Assert.IsNotNull(order); + + await repository.UpdateAsync(order, default); + await repository.UnitOfWork.SaveChangesAsync(); + dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; + Assert.IsTrue(await repository.GetCountAsync(default) == 1); + + order = await repository.FindAsync(order => order.Description == "Apple"); + Assert.IsNotNull(order); + + order.Description = "Apple Company"; + await repository.UpdateRangeAsync(new List() { order }, default); + await repository.UnitOfWork.SaveChangesAsync(); + + dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; + + order = await repository.FindAsync(order => order.Description == "Apple"); + Assert.IsNull(order); } [TestMethod] - public void TestCustomRepository() + public void TestCompositeKeys() { - var serviceProvider = base.CreateDefaultServiceProvider(_assemblies)!; - IOrderRepository orderRepository = serviceProvider.GetRequiredService(); - Assert.IsNotNull(orderRepository); - orderRepository.AddAsync(new Orders() + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + Assert.ThrowsException(() => { - BuyerName = "lisa" + _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Students).Assembly); }); } [TestMethod] - public void TestAddMultRepository() + public void TestErrorCompositeKeys() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Courses).Assembly); + }); + } + + [TestMethod] + public void TestPrivateEntity() { - var services = new ServiceCollection(); - Mock unitOfWork = new(); - services.AddScoped(typeof(IUnitOfWork), serviceProvider => unitOfWork.Object); - services.AddDbContext(options => options.UseSqlite(_connection)); - new DispatcherOptions(services).UseRepository(_assemblies).UseRepository(_assemblies); - - var serviceProvider = services.BuildServiceProvider(); - var repository = serviceProvider.GetServices>(); - Assert.IsTrue(repository.Count() == 1); + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Hobbies).Assembly); } } diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs index a99971ce8..574d3fdd8 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs @@ -1,6 +1,6 @@ namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests; -public class TestBase +public class TestBase : IDisposable { protected readonly SqliteConnection _connection; @@ -14,24 +14,4 @@ public void Dispose() { _connection.Close(); } - - - protected IServiceProvider CreateDefaultServiceProvider(params Assembly[] assemblies) - { - return CreateServiceProvider(services => - { - Mock unitOfWork = new(); - services.AddScoped(typeof(IUnitOfWork), serviceProvider => unitOfWork.Object); - services.AddDbContext(options => options.UseSqlite(_connection)); - }, assemblies); - } - - protected IServiceProvider CreateServiceProvider(Action? action, params Assembly[] assemblies) - { - var services = new ServiceCollection(); - action?.Invoke(services); - - new DispatcherOptions(services).UseRepository(assemblies); - return services.BuildServiceProvider(); - } } diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs index 763686eef..0d939826d 100644 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs +++ b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs @@ -1,14 +1,16 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.DDD.Domain.Entities; global using MASA.BuildingBlocks.DDD.Domain.Entities.Auditing; global using MASA.BuildingBlocks.DDD.Domain.Repositories; global using MASA.BuildingBlocks.DDD.Domain.Values; global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Repositories; +global using MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests; +global using MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests; +global using MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Repositories; +global using MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests; global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Repositories; global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure; -global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure.Options; global using Microsoft.Data.Sqlite; global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.DependencyInjection; @@ -18,3 +20,4 @@ global using System.Collections.Generic; global using System.Linq; global using System.Reflection; + diff --git a/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs b/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs index f4b86aeaa..9365bd391 100644 --- a/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs +++ b/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs @@ -1,95 +1,127 @@ namespace MASA.Contrib.DDD.Domain.Tests; [TestClass] -public class DomainEventBusTest : TestBase +public class DomainEventBusTest { + private Assembly[] _defaultAssemblies = default!; + private IServiceCollection _services = default!; + private Mock _eventBus = default!; + private Mock _integrationEventBus = default!; + private Mock _uoW = default!; + private IOptions _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _defaultAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + _services = new ServiceCollection(); + _eventBus = new(); + _integrationEventBus = new(); + _uoW = new(); + _dispatcherOptions = Options.Create(new DispatcherOptions(new ServiceCollection())); + } + + [TestMethod] + public void TestGetAllEventTypes() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var eventTypes = assemblies.SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)); + _eventBus.Setup(eventBus => eventBus.GetAllEventTypes()).Returns(() => eventTypes); + _dispatcherOptions.Value.Assemblies = _defaultAssemblies; + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + + Assert.IsTrue(domainEventBus.GetAllEventTypes().Count() == eventTypes.Count(), ""); + } + [TestMethod] public async Task TestPublishDomainEventAsync() { - PaymentSucceededDomainEvent @event = new PaymentSucceededDomainEvent() - { - OrderId = new Random().Next(10000, 1000000).ToString() - }; - var serviceProvider = CreateDefaultProvider(); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + + var domainEvent = new PaymentSucceededDomainEvent(new Random().Next(10000, 1000000).ToString()); + await domainEventBus.PublishAsync(domainEvent); - Assert.IsTrue(eventBus.GetAllEventTypes().Count() == 5); + _eventBus.Verify(eventBus => eventBus.PublishAsync(domainEvent), Times.Once, "PublishAsync is executed multiple times"); + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(domainEvent), Times.Never, "integrationEventBus should not be executed"); + Assert.IsTrue(domainEvent.UnitOfWork!.Equals(_uoW.Object)); } [TestMethod] public async Task TestPublishIntegrationDomainEventAsync() { - PaymentFailedIntegrationDomainEvent @event = new PaymentFailedIntegrationDomainEvent() + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _integrationEventBus.Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())).Verifiable(); + var integrationDomainEvent = new PaymentFailedIntegrationDomainEvent() { OrderId = new Random().Next(10000, 1000000).ToString() }; - var serviceProvider = CreateDefaultProvider(); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); + await domainEventBus.PublishAsync(integrationDomainEvent); + + _eventBus.Verify(eventBus => eventBus.PublishAsync(integrationDomainEvent), Times.Never, "eventBus should not be executed"); + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Once, " PublishAsync is executed multiple times"); } [TestMethod] public async Task TestPublishDomainCommandAsync() { + _uoW.Setup(u => u.CommitAsync(default)).Verifiable(); + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())) + .Callback((domainEvent) => + { + Mock> userRepository = new(); + var user = new Users() + { + Name = "Jim" + }; + userRepository.Setup(repository => repository.AddAsync(It.IsAny(), CancellationToken.None)).Verifiable(); + domainEvent.UnitOfWork!.CommitAsync(); + }); + var @command = new CreateProductDomainCommand() { Name = "Phone" }; + await domainEventBus.PublishAsync(@command); - var serviceProvider = CreateDefaultProvider(); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@command); + _eventBus.Verify(eventBus => eventBus.PublishAsync(@command), Times.Once, "PublishAsync is executed multiple times"); + _uoW.Verify(u => u.CommitAsync(default), Times.Once); } [TestMethod] - public async Task TestAddMultDomainEventBusAsync() + public void TestAddMultDomainEventBusAsync() { - var services = new ServiceCollection(); - - services.AddDomainEventBus(options => - { - options.Assemblies = new System.Reflection.Assembly[1] { typeof(TestBase).Assembly }; - Mock eventBus = new(); - eventBus.Setup(e => e.PublishAsync(It.IsAny())).Verifiable(); - services.AddScoped(typeof(IEventBus), serviceProvider => eventBus.Object); - - Mock unitOfWork = new(); - services.AddScoped(typeof(IUnitOfWork), serviceProvider => unitOfWork.Object); + _services.AddScoped(serviceProvider => _eventBus.Object); + _services.AddScoped(serviceProvider => _integrationEventBus.Object); + _services.AddScoped(serviceProvider => _uoW.Object); - Mock integrationEventBus = new(); - integrationEventBus.Setup(e => e.PublishAsync(It.IsAny())).Verifiable(); - services.AddScoped(typeof(IIntegrationEventBus), serviceProvider => integrationEventBus.Object); - }).AddDomainEventBus(); - - var serviceProvider = services.BuildServiceProvider(); + _services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }).AddDomainEventBus(); + var serviceProvider = _services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - - var userDomainService = serviceProvider.GetService(); - Assert.IsNotNull(userDomainService); - - Assert.IsTrue(await userDomainService.RegisterUserSucceededAsync("tom") == "succeed"); + Assert.IsTrue(serviceProvider.GetServices>().Count() == 1); } [TestMethod] public void TestNotUseEventBus() { - var services = new ServiceCollection(); - - var ex = Assert.ThrowsException(() => services.AddDomainEventBus()); + var ex = Assert.ThrowsException(() + => _services.AddDomainEventBus() + ); Assert.IsTrue(ex.Message == "Please add EventBus first."); } [TestMethod] public void TestNotUseUnitOfWork() { - var services = new ServiceCollection(); - var eventBus = new Mock(); - services.AddScoped(serviceProvider => eventBus.Object); + _services.AddScoped(serviceProvider => eventBus.Object); - var ex = Assert.ThrowsException(() => services.AddDomainEventBus()); - Assert.IsTrue(ex.Message == "Please add Uow first."); + var ex = Assert.ThrowsException(() + => _services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }) + ); + Assert.IsTrue(ex.Message == "Please add UoW first."); } [TestMethod] @@ -100,28 +132,19 @@ public void TestNotUseIntegrationEventBus() var eventBus = new Mock(); services.AddScoped(serviceProvider => eventBus.Object); - var uow = new Mock(); - services.AddScoped(serviceProvider => uow.Object); + var uoW = new Mock(); + services.AddScoped(serviceProvider => uoW.Object); - var ex = Assert.ThrowsException(() => services.AddDomainEventBus()); + var ex = Assert.ThrowsException(() + => services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }) + ); Assert.IsTrue(ex.Message == "Please add IntegrationEventBus first."); } [TestMethod] public void TestNullAssembly() { - var services = new ServiceCollection(); - - var eventBus = new Mock(); - services.AddScoped(serviceProvider => eventBus.Object); - - var uow = new Mock(); - services.AddScoped(serviceProvider => uow.Object); - - var integrationEventBus = new Mock(); - services.AddScoped(serviceProvider => integrationEventBus.Object); - - Assert.ThrowsException(() => services.AddDomainEventBus(options => { options.Assemblies = null; })); + Assert.ThrowsException(() => _dispatcherOptions.Value.Assemblies = null!); } [TestMethod] @@ -132,8 +155,8 @@ public void TestNotRepository() var eventBus = new Mock(); services.AddScoped(serviceProvider => eventBus.Object); - var uow = new Mock(); - services.AddScoped(serviceProvider => uow.Object); + var uoW = new Mock(); + services.AddScoped(serviceProvider => uoW.Object); var integrationEventBus = new Mock(); services.AddScoped(serviceProvider => integrationEventBus.Object); @@ -142,12 +165,11 @@ public void TestNotRepository() { services.AddDomainEventBus(options => { - options.Assemblies = new System.Reflection.Assembly[1] { typeof(User).Assembly }; + options.Assemblies = new Assembly[1] { typeof(Users).Assembly }; }); }); } - [TestMethod] public void TestUserRepository() { @@ -156,92 +178,172 @@ public void TestUserRepository() var eventBus = new Mock(); services.AddScoped(serviceProvider => eventBus.Object); - var uow = new Mock(); - services.AddScoped(serviceProvider => uow.Object); + var uoW = new Mock(); + services.AddScoped(serviceProvider => uoW.Object); var integrationEventBus = new Mock(); services.AddScoped(serviceProvider => integrationEventBus.Object); - services.AddScoped, UserRepository>(); + + Mock> repository = new(); + services.AddScoped(serviceProvider => repository.Object); services.AddDomainEventBus(options => { - options.Assemblies = new System.Reflection.Assembly[2] { typeof(User).Assembly, typeof(UserRepository).Assembly }; + options.Assemblies = new Assembly[2] { typeof(Users).Assembly, typeof(DomainEventBusTest).Assembly }; }); } [TestMethod] public async Task TestPublishQueueAsync() { - var services = new ServiceCollection(); - - //todo: Temporary results, used to show the enqueue and dequeue order - int result = 0; - - Mock eventBus = new(); - eventBus - .Setup(e => e.PublishAsync(It.IsAny())) - .Callback(async cmd => - { - if (result == 0) - { - result = 3; - } - else - { - result = 4; - } - await Task.FromResult(result); - }); - Mock integrationEventBus = new(); - integrationEventBus - .Setup(e => e.PublishAsync(It.IsAny())) - .Callback(async cmd => + var domainEvent = new PaymentSucceededDomainEvent("ef5f84db-76e4-4c79-9815-99a1543b6589"); + var integrationDomainEvent = new PaymentFailedIntegrationDomainEvent { OrderId = "d65c1a0c-6e44-40ce-9737-738fa1dcdab4" }; + + _eventBus + .Setup(eventBus => eventBus.PublishAsync(It.IsAny())) + .Callback(() => + { + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Never, "Sent in the wrong order"); + }); + + _integrationEventBus + .Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())) + .Callback(() => { - if (result == 3) - { - result = 1; - } - else - { - result = 2; - } - await Task.FromResult(result); + _eventBus.Verify(eventBus => eventBus.PublishAsync((IDomainEvent)domainEvent), Times.Once, "Sent in the wrong order"); }); - var uow = new Mock(); - uow.Setup(u => u.CommitAsync(default)).Verifiable(); + var uoW = new Mock(); + uoW.Setup(u => u.CommitAsync(default)).Verifiable(); - var options = Options.Create(new DispatcherOptions(services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); + var options = Options.Create(new DispatcherOptions(_services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); + + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, uoW.Object, options); - var domainEventBus = new DomainEventBus(eventBus.Object, integrationEventBus.Object, uow.Object, options); + await domainEventBus.Enqueue(domainEvent); + await domainEventBus.Enqueue(integrationDomainEvent); - // todo: It has no practical meaning, just to show the order of entering and leaving the team - await domainEventBus.Enqueue(new PaymentSucceededDomainEvent() { OrderId = "ef5f84db-76e4-4c79-9815-99a1543b6589" }); - await domainEventBus.Enqueue(new PaymentFailedIntegrationDomainEvent() { OrderId = "d65c1a0c-6e44-40ce-9737-738fa1dcdab4" }); await domainEventBus.PublishQueueAsync(); - Assert.IsTrue(result == 1); + + _eventBus.Verify(eventBus => eventBus.PublishAsync((IDomainEvent)domainEvent), Times.Once, "Sent in the wrong order"); + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Never, "Sent in the wrong order"); } [TestMethod] - public async Task TestPublishDomainQuery() + public async Task TestPublishDomainQueryAsync() { var services = new ServiceCollection(); var eventBus = new Mock(); eventBus.Setup(e => e.PublishAsync(It.IsAny())) - .Callback(async query => + .Callback(query => { - query.Result = "apple"; + if (query.ProductId == "2f8d4c3c-1736-4e56-a188-f865da6a63d1") + query.Result = "apple"; }); var integrationEventBus = new Mock(); - var uow = new Mock(); - uow.Setup(u => u.CommitAsync(default)).Verifiable(); + var uoW = new Mock(); + uoW.Setup(u => u.CommitAsync(default)).Verifiable(); var options = Options.Create(new DispatcherOptions(services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); - var domainEventBus = new DomainEventBus(eventBus.Object, integrationEventBus.Object, uow.Object, options); + var domainEventBus = new DomainEventBus(eventBus.Object, integrationEventBus.Object, uoW.Object, options); var query = new ProductItemDomainQuery() { ProductId = "2f8d4c3c-1736-4e56-a188-f865da6a63d1" }; + await domainEventBus.PublishAsync(query); Assert.IsTrue(query.Result == "apple"); } + + [TestMethod] + public async Task TestCommitAsync() + { + var services = new ServiceCollection(); + + _uoW.Setup(uow => uow.CommitAsync(CancellationToken.None)).Verifiable(); + Mock> options = new(); + + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, options.Object); + await domainEventBus.CommitAsync(CancellationToken.None); + + _uoW.Verify(u => u.CommitAsync(default), Times.Once, "CommitAsync must be called only once"); + } + + [TestMethod] + public void TestParameterInitialization() + { + var id = Guid.NewGuid(); + var createTime = DateTime.UtcNow; + + var domainCommand = new DomainCommand(); + Assert.IsTrue(domainCommand.Id != default); + Assert.IsTrue(domainCommand.CreationTime != default && domainCommand.CreationTime >= createTime); + + domainCommand = new DomainCommand(id, createTime); + Assert.IsTrue(domainCommand.Id == id); + Assert.IsTrue(domainCommand.CreationTime == createTime); + + var domainEvent = new DomainEvent(); + Assert.IsTrue(domainEvent.Id != default); + Assert.IsTrue(domainEvent.CreationTime != default && domainEvent.CreationTime >= createTime); + + domainEvent = new DomainEvent(id, createTime); + Assert.IsTrue(domainEvent.Id == id); + Assert.IsTrue(domainEvent.CreationTime == createTime); + + var domainQuery = new ProductItemDomainQuery() + { + ProductId = Guid.NewGuid().ToString() + }; + Assert.IsTrue(domainQuery.Id != default); + Assert.IsTrue(domainQuery.CreationTime != default && domainQuery.CreationTime >= createTime); + } + + [TestMethod] + public void TestDomainQueryUnitOfWork() + { + var domainQuery = new ProductItemDomainQuery() + { + ProductId = Guid.NewGuid().ToString() + }; + Assert.ThrowsException(() => + { + domainQuery.UnitOfWork = _uoW.Object; + }); + Assert.IsNull(domainQuery.UnitOfWork); + } + + [TestMethod] + public async Task TestDomainServiceAsync() + { + _integrationEventBus.Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())).Verifiable(); + + _services.AddDomainEventBus(options => + { + options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }; + options.Services.AddScoped(serviceProvider => _eventBus.Object); + options.Services.AddScoped(serviceProvider => _integrationEventBus.Object); + options.Services.AddScoped(serviceProvider => _uoW.Object); + }); + var serviceProvider = _services.BuildServiceProvider(); + + var userDomainService = serviceProvider.GetRequiredService(); + var domainIntegrationEvent = new RegisterUserSucceededDomainIntegrationEvent() { Account = "Tom" }; + await userDomainService.RegisterUserSucceededAsync(domainIntegrationEvent); + + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(domainIntegrationEvent), Times.Once); + } + + [TestMethod] + public async Task TestPublishEvent() + { + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + + var @event = new ForgetPasswordEvent() + { + Account = "Tom" + }; + await domainEventBus.PublishAsync(@event); + _eventBus.Verify(eventBus => eventBus.PublishAsync(@event), Times.Once); + } } diff --git a/test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs b/test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs new file mode 100644 index 000000000..1aeb39d9a --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs @@ -0,0 +1,49 @@ +๏ปฟnamespace MASA.Contrib.DDD.Domain.Tests; + +[TestClass] +public class DomainIntegrationEventBus +{ + private Assembly[] _defaultAssemblies = default!; + private IServiceCollection _services = default!; + private Mock _integrationEventBus = default!; + private Mock _uoW = default!; + private IOptions _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _defaultAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + _services = new ServiceCollection(); + _integrationEventBus = new(); + _integrationEventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + _uoW = new(); + _dispatcherOptions = Options.Create(new DispatcherOptions(new ServiceCollection())); + } + + [TestMethod] + public async Task PublishQueueAsync() + { + _services.AddEventBus(opt => + { + opt.Assemblies = _defaultAssemblies; + }); + var serviceProvider = _services.BuildServiceProvider(); + var eventBus = serviceProvider.GetRequiredService(); + var payment = new + { + orderId = Guid.NewGuid(), + money = 100, + payTime = DateTime.UtcNow + }; + var domainEventBus = new DomainEventBus(eventBus, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + + var domainEvent = new PaymentSucceededDomainEvent(payment.orderId.ToString()); + await domainEventBus.Enqueue(domainEvent); + + var integraionDomainEvent = new PaymentSucceededIntegraionDomainEvent(payment.orderId.ToString(), payment.money, payment.payTime); + await domainEventBus.Enqueue(integraionDomainEvent); + await domainEventBus.PublishQueueAsync(); + Assert.IsTrue(domainEvent.Result); + _integrationEventBus.Verify(eventBus => eventBus.PublishAsync(It.IsAny()), Times.Once); + } +} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/EventHandlers/PaymentSucceededHandlers.cs b/test/MASA.Contrib.DDD.Domain.Tests/EventHandlers/PaymentSucceededHandlers.cs deleted file mode 100644 index 23a753bc0..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/EventHandlers/PaymentSucceededHandlers.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.EventHandlers; - -public class PaymentSucceededHandlers : IEventHandler -{ - private readonly ILogger _logger; - - public PaymentSucceededHandlers(ILogger logger) => _logger = logger; - - public Task HandleAsync(PaymentSucceededDomainEvent @event) - { - _logger.LogInformation("Publishing PaymentSucceededDomainEvent {@Event} on {CreationTime}", @event, @event.CreationTime); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs new file mode 100644 index 000000000..7e8bdcf94 --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs @@ -0,0 +1,10 @@ +namespace MASA.Contrib.DDD.Domain.Tests.Events; + +public class ForgetPasswordEvent : IEvent +{ + public Guid Id { get; init; } = Guid.NewGuid(); + + public DateTime CreationTime { get; init; } = DateTime.UtcNow; + + public string Account { get; set; } +} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs index 4aa55e063..70d3a5098 100644 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs +++ b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs @@ -2,12 +2,7 @@ namespace MASA.Contrib.DDD.Domain.Tests.Events; public record PaymentFailedIntegrationDomainEvent : IntegrationDomainEvent { - public PaymentFailedIntegrationDomainEvent() - { - Topic = typeof(PaymentFailedIntegrationDomainEvent).Name; - } - public string OrderId { get; set; } - public override string Topic { get; set; } + public override string Topic { get; set; } = nameof(PaymentFailedIntegrationDomainEvent); } diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs index cdfe314a9..5b38d08b8 100644 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs +++ b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs @@ -1,7 +1,6 @@ namespace MASA.Contrib.DDD.Domain.Tests.Events; -public record PaymentSucceededDomainEvent : DomainEvent +public record PaymentSucceededDomainEvent(string OrderId) : DomainEvent { - public string OrderId { get; set; } + public bool Result { get; set; } = false; } - diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs new file mode 100644 index 000000000..44719cdbf --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs @@ -0,0 +1,6 @@ +๏ปฟnamespace MASA.Contrib.DDD.Domain.Tests.Events; + +public record PaymentSucceededIntegraionDomainEvent(string OrderId, decimal Money, DateTime PayTime) : IntegrationDomainEvent +{ + public override string Topic { get; set; } = nameof(PaymentSucceededIntegraionDomainEvent); +} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs b/test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs new file mode 100644 index 000000000..2127a6ea6 --- /dev/null +++ b/test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs @@ -0,0 +1,19 @@ +๏ปฟnamespace MASA.Contrib.DDD.Domain.Tests.Handlers; + +public class PaymentSucceededDomainEventHandller +{ + private readonly ILogger? _logger; + + public PaymentSucceededDomainEventHandller(ILogger? logger = null) + { + _logger = logger; + } + + [EventHandler] + public Task PaymentSucceeded(PaymentSucceededDomainEvent domainEvent) + { + _logger?.LogInformation("PaymentSucceeded: OrderId: {OrderId}", domainEvent.OrderId); + domainEvent.Result = true; + return Task.CompletedTask; + } +} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj index 7d8fc395f..3753ec12d 100644 --- a/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj +++ b/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj @@ -1,4 +1,4 @@ - +๏ปฟ net6.0 @@ -8,17 +8,22 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + - + + diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Repositories/UserRepository.cs b/test/MASA.Contrib.DDD.Domain.Tests/Repositories/UserRepository.cs deleted file mode 100644 index 010fb49b6..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Repositories/UserRepository.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Repositories; - -public class UserRepository : IRepository -{ - public IUnitOfWork UnitOfWork => throw new NotImplementedException(); - - public ValueTask AddAsync(User entity, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public ValueTask FindAsync(params object?[]? keyValues) - { - throw new NotImplementedException(); - } - - public ValueTask FindAsync(object?[]? keyValues, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task FindAsync(Expression> predicate, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task GetCountAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task GetCountAsync(Expression> predicate, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task> GetListAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task> GetListAsync(Expression> predicate, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task> GetPaginatedListAsync(int skip, int take, string? sorting, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task> GetPaginatedListAsync(Expression> predicate, int skip, int take, string? sorting, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task> GetPaginatedListAsync(PaginatedOptions options, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task> GetPaginatedListAsync(Expression> predicate, PaginatedOptions options, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task RemoveAsync(User entity, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task RemoveAsync(Expression> predicate, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task RemoveRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task UpdateAsync(User entity, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - - public Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs b/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs index 61a112e95..52065a304 100644 --- a/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs +++ b/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs @@ -6,11 +6,11 @@ public UserDomainService(IDomainEventBus eventBus) : base(eventBus) { } - public async Task RegisterUserSucceededAsync(string account) + public async Task RegisterUserSucceededAsync(RegisterUserSucceededDomainIntegrationEvent domainIntegrationEvent) { // TODO Simulate a successful message for registered users - await EventBus.PublishAsync(new RegisterUserSucceededDomainIntegrationEvent() { Account = account }); + await EventBus.PublishAsync(domainIntegrationEvent); return "succeed"; } } diff --git a/test/MASA.Contrib.DDD.Domain.Tests/TestBase.cs b/test/MASA.Contrib.DDD.Domain.Tests/TestBase.cs deleted file mode 100644 index cd3f308cc..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/TestBase.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests; - -public class TestBase -{ - protected const string DAPR_PUBSUB_NAME = "pubsub"; - - protected IServiceProvider CreateDefaultProvider() - { - return CreateProvider((services) => - { - Mock integrationEventBus = new(); - integrationEventBus.Setup(e => e.PublishAsync(It.IsAny())).Verifiable(); - services.AddScoped(typeof(IIntegrationEventBus), serviceProvider => integrationEventBus.Object); - }); - } - - protected IServiceProvider CreateProvider(Action? action = null) - { - var services = new ServiceCollection(); - action?.Invoke(services); - services.AddDomainEventBus(options => - { - options.Assemblies = new System.Reflection.Assembly[1] { typeof(TestBase).Assembly }; - Mock eventBus = new(); - eventBus.Setup(e => e.PublishAsync(It.IsAny())).Verifiable(); - services.AddScoped(typeof(IEventBus), serviceProvider => eventBus.Object); - - Mock unitOfWork = new(); - services.AddScoped(typeof(IUnitOfWork), serviceProvider => unitOfWork.Object); - }); - return services.BuildServiceProvider(); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs index 3415e4957..e1a0ca7d6 100644 --- a/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs +++ b/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs @@ -1,16 +1,16 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.DDD.Domain.Events; global using MASA.BuildingBlocks.DDD.Domain.Repositories; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; global using MASA.Contrib.DDD.Domain.Events; global using MASA.Contrib.DDD.Domain.Tests.Events; -global using MASA.Contrib.DDD.Domain.Tests.Repositories; global using MASA.Contrib.DDD.Domain.Tests.Services; -global using MASA.Contribs.DDD.Domain.Entities; +global using MASA.Contribs.DDD.Domain.Entities.Tests; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; -global using System.Linq.Expressions; +global using System.Reflection; +global using MASA.Contrib.Dispatcher.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs new file mode 100644 index 000000000..2b6039096 --- /dev/null +++ b/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs @@ -0,0 +1,14 @@ +namespace MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; + +public class Courses : AggregateRoot +{ + public Courses() + { + Id = Guid.NewGuid(); + IsDeleted = false; + } + + public string Name { get; set; } + + public bool IsDeleted { get; set; } +} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs new file mode 100644 index 000000000..968c5de7c --- /dev/null +++ b/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs @@ -0,0 +1,16 @@ +namespace MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; + +public class Students : AuditAggregateRoot +{ + public Students() + { + Id = Guid.NewGuid(); + RegisterTime = DateTime.UtcNow; + } + + public string Name { get; set; } + + public int Age { get; set; } + + public DateTime RegisterTime { get; private set; } +} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs new file mode 100644 index 000000000..46299964b --- /dev/null +++ b/test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs @@ -0,0 +1,12 @@ +using MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; + +namespace MASA.Contrib.Data.Contracts.EF.Tests.Infrastructure; + +public class CustomDbContext : MasaDbContext +{ + public DbSet Students { get; set; } + + public DbSet Courses { get; set; } + + public CustomDbContext(MasaDbContextOptions options) : base(options) { } +} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj b/test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj new file mode 100644 index 000000000..02aa8883f --- /dev/null +++ b/test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj @@ -0,0 +1,28 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs new file mode 100644 index 000000000..0a427c46e --- /dev/null +++ b/test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs @@ -0,0 +1,117 @@ +using MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; +using MASA.Contrib.Data.Contracts.EF.Tests.Infrastructure; + +namespace MASA.Contrib.Data.Contracts.EF.Tests; + +[TestClass] +public class SoftDeleteTest : IDisposable +{ + protected readonly SqliteConnection _connection; + + public SoftDeleteTest() + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + } + + public void Dispose() + { + _connection.Close(); + } + + [TestMethod] + public void UseNotUseUoW() + { + var services = new ServiceCollection(); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + Assert.ThrowsException(() => option.UseSoftDelete(services), "Please add UoW first."); + }); + } + + [TestMethod] + public void TestUseSoftDelete() + { + Mock uoW = new(); + uoW.Setup(u => u.Transaction).Verifiable(); + var services = new ServiceCollection(); + services.AddScoped(serviceProvider => uoW.Object); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + option.UseSoftDelete(services); + }); + + var serviceProvider = services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + dbContext.Set().Add(new Students() + { + Name = "Tom", + Age = 18 + }); + dbContext.SaveChanges(); + Assert.IsTrue(dbContext.Students.Count() == 1); + uoW.Verify(u => u.Transaction, Times.Never); + + var student = dbContext.Students.FirstOrDefault(s => s.Name == "Tom"); + Assert.IsNotNull(student); + dbContext.Set().Remove(student); + dbContext.SaveChanges(); + + Assert.IsTrue(!dbContext.Students.Any()); + + student.IsDeleted = false; + dbContext.SaveChanges(); + Assert.IsTrue(dbContext.Students.Count() == 1); + + uoW = new(); + uoW.Setup(u => u.Transaction).Verifiable(); + uoW.Setup(u => u.UseTransaction).Returns(() => true); + services = new ServiceCollection(); + services.AddScoped(serviceProvider => uoW.Object); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + option.UseSoftDelete(services); + }); + + serviceProvider = services.BuildServiceProvider(); + dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + dbContext.Set().Add(new Courses() + { + Name = "Chinese" + }); + dbContext.SaveChanges(); + Assert.IsTrue(dbContext.Courses.Count() == 1); + uoW.Verify(u => u.Transaction, Times.Never); + + var course = dbContext.Set().FirstOrDefault(c => c.Name == "Chinese"); + Assert.IsNotNull(course); + dbContext.Set().Remove(course); + dbContext.SaveChanges(); + Assert.IsTrue(!dbContext.Courses.Any()); + + course.IsDeleted = false; + dbContext.SaveChanges(); + Assert.IsTrue(!dbContext.Courses.Any()); + } + + [TestMethod] + public void TestUseMultiSoftDelete() + { + Mock uoW = new(); + uoW.Setup(u => u.Transaction).Verifiable(); + var services = new ServiceCollection(); + services.AddScoped(serviceProvider => uoW.Object); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + option.UseSoftDelete(services).UseSoftDelete(services); + }); + } +} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs new file mode 100644 index 000000000..13d3abe9d --- /dev/null +++ b/test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs @@ -0,0 +1,10 @@ +global using MASA.BuildingBlocks.Data.UoW; +global using MASA.BuildingBlocks.DDD.Domain.Entities; +global using MASA.BuildingBlocks.DDD.Domain.Entities.Auditing; +global using MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; +global using MASA.Utils.Data.EntityFrameworkCore; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; diff --git a/test/MASA.Contrib.Data.Uow.EF.Tests/CustomerDbContext.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs similarity index 73% rename from test/MASA.Contrib.Data.Uow.EF.Tests/CustomerDbContext.cs rename to test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs index 92d31bad0..ce3b81dda 100644 --- a/test/MASA.Contrib.Data.Uow.EF.Tests/CustomerDbContext.cs +++ b/test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs @@ -1,9 +1,12 @@ -using MASA.Utils.Data.EntityFrameworkCore; - -namespace MASA.Contrib.Data.Uow.EF.Tests; +namespace MASA.Contrib.Data.UoW.EF.Tests; public class CustomerDbContext : MasaDbContext { + public CustomerDbContext() + { + + } + public CustomerDbContext(MasaDbContextOptions options) : base(options) { } public DbSet User { get; set; } @@ -30,7 +33,12 @@ void ConfigureUserEntry(EntityTypeBuilder builder) public class Users { - public Guid Id { get; set; } + public Guid Id { get; private set; } + + public string Name { get; set; } = default!; - public string Name { get; set; } + public Users() + { + this.Id = Guid.NewGuid(); + } } diff --git a/test/MASA.Contrib.Data.Uow.EF.Tests/MASA.Contrib.Data.Uow.EF.Tests.csproj b/test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj similarity index 75% rename from test/MASA.Contrib.Data.Uow.EF.Tests/MASA.Contrib.Data.Uow.EF.Tests.csproj rename to test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj index 2f7ff853c..78360345b 100644 --- a/test/MASA.Contrib.Data.Uow.EF.Tests/MASA.Contrib.Data.Uow.EF.Tests.csproj +++ b/test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj @@ -8,8 +8,12 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -21,7 +25,7 @@ - + diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs new file mode 100644 index 000000000..878c8982e --- /dev/null +++ b/test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs @@ -0,0 +1,17 @@ +namespace MASA.Contrib.Data.UoW.EF.Tests; + +public class TestBase : IDisposable +{ + protected readonly SqliteConnection _connection; + + protected TestBase() + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + } + + public void Dispose() + { + _connection.Close(); + } +} diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs new file mode 100644 index 000000000..be9a58037 --- /dev/null +++ b/test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -0,0 +1,171 @@ +namespace MASA.Contrib.Data.UoW.EF.Tests; + +[TestClass] +public class TestUnitOfWork : TestBase +{ + private Mock _options; + + [TestInitialize] + public void Initialize() + { + _options = new(); + _options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); + } + + [TestMethod] + public void TestAddUoWAndNullServices() + { + var options = new Mock(); + Assert.ThrowsException(() => options.Object.UseUoW()); + } + + [TestMethod] + public void TestAddUoW() + { + _options.Object.UseUoW(); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + Assert.ThrowsException(() + => serviceProvider.GetRequiredService() + ); + } + + [TestMethod] + public void TestAddUoWAndUseSqlLite() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + Assert.IsNotNull(serviceProvider.GetRequiredService()); + } + + [TestMethod] + public void TestAddMultUoW() + { + _options.Object + .UseUoW(options => options.UseSqlite(_connection)) + .UseUoW(options => options.UseSqlite(_connection)); + + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestTransaction() + { + Mock uoW = new(); + Assert.IsTrue(new Transaction(uoW.Object).UnitOfWork!.Equals(uoW.Object)); + } + + [TestMethod] + public async Task TestSaveChangesAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + Mock customerDbContext = new(); + customerDbContext.Setup(dbContext => dbContext.SaveChangesAsync(default)).Verifiable(); + var uoW = new UnitOfWork(customerDbContext.Object, null); + await uoW.SaveChangesAsync(default); + customerDbContext.Verify(dbContext => dbContext.SaveChangesAsync(default), Times.Once); + } + + [TestMethod] + public async Task TestUseTranscationAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = serviceProvider.GetRequiredService(); + + var transaction = uoW.Transaction; + Users user = new Users() + { + Name = Guid.NewGuid().ToString() + }; + dbContext.Add(user); + await uoW.SaveChangesAsync(); + await uoW.RollbackAsync(); + + Assert.IsTrue(dbContext.User.ToList().Count() == 0); + } + + [TestMethod] + public async Task TestNotUseTranscationAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = new UnitOfWork(dbContext, null); + + Users user = new Users() + { + Name = Guid.NewGuid().ToString() + }; + dbContext.Add(user); + await uoW.SaveChangesAsync(); + await Assert.ThrowsExceptionAsync(async () => await uoW.RollbackAsync()); + } + + [TestMethod] + public async Task TestNotTransactionCommitAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = new UnitOfWork(dbContext, null); + await Assert.ThrowsExceptionAsync(async () => await uoW.CommitAsync()); + } + + [TestMethod] + public async Task TestCommitAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = new UnitOfWork(dbContext, null); + var user = new Users() + { + Name = "Tom" + }; + var transcation = uoW.Transaction; + dbContext.User.Add(user); + await uoW.SaveChangesAsync(); + await uoW.CommitAsync(); + + Assert.IsTrue(dbContext.User.ToList().Count == 1); + } + + [TestMethod] + public async Task TestOpenRollbackAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = serviceProvider.GetRequiredService(); + var user = new Users(); + var transcation = uoW.Transaction; + dbContext.User.Add(user); + await uoW.CommitAsync(); + + Assert.IsTrue(!await dbContext.User.AnyAsync()); + } + + [TestMethod] + public async Task TestAddLoggerAndOpenRollbackAsync() + { + _options.Object.Services.AddLogging(); + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = serviceProvider.GetRequiredService(); + var user = new Users(); + var transcation = uoW.Transaction; + dbContext.User.Add(user); + await uoW.CommitAsync(); + + Assert.IsTrue(!await dbContext.User.AnyAsync()); + } +} diff --git a/test/MASA.Contrib.Data.Uow.EF.Tests/_Imports.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs similarity index 72% rename from test/MASA.Contrib.Data.Uow.EF.Tests/_Imports.cs rename to test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs index dafc00074..1af16327c 100644 --- a/test/MASA.Contrib.Data.Uow.EF.Tests/_Imports.cs +++ b/test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs @@ -1,5 +1,7 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.Dispatcher.Events; +global using MASA.Utils.Data.EntityFrameworkCore; +global using Microsoft.Data.Sqlite; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Metadata.Builders; global using Microsoft.Extensions.DependencyInjection; diff --git a/test/MASA.Contrib.Data.Uow.EF.Tests/TestBase.cs b/test/MASA.Contrib.Data.Uow.EF.Tests/TestBase.cs deleted file mode 100644 index 20eb11ab3..000000000 --- a/test/MASA.Contrib.Data.Uow.EF.Tests/TestBase.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Data.Sqlite; - -namespace MASA.Contrib.Data.Uow.EF.Tests -{ - public class TestBase : IDisposable - { - protected readonly SqliteConnection _connection; - - protected TestBase() - { - _connection = new SqliteConnection("DataSource=:memory:"); - _connection.Open(); - } - - public void Dispose() - { - _connection.Close(); - } - - private IServiceProvider CreateDefaultProvider() - { - var options = new Mock(); - options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); - options.Object.UseUoW(options => options.UseSqlite(_connection)); - return options.Object.Services.BuildServiceProvider(); - } - - protected (IServiceProvider serviceProvider, CustomerDbContext dbContext) CreateDefault() - { - var serviceProvider = CreateDefaultProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - return (serviceProvider, dbContext); - } - } -} diff --git a/test/MASA.Contrib.Data.Uow.EF.Tests/TestUnitOfWork.cs b/test/MASA.Contrib.Data.Uow.EF.Tests/TestUnitOfWork.cs deleted file mode 100644 index 2a7956258..000000000 --- a/test/MASA.Contrib.Data.Uow.EF.Tests/TestUnitOfWork.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace MASA.Contrib.Data.Uow.EF.Tests; - -[TestClass] -public class TestUnitOfWork : TestBase -{ - [TestMethod] - public void TestAddUowAndNullServices() - { - var options = new Mock(); - Assert.ThrowsException(() => options.Object.UseUoW()); - } - - [TestMethod] - public void TestAddUow() - { - var options = new Mock(); - options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); - options.Object.UseUoW(); - var serviceProvider = options.Object.Services.BuildServiceProvider(); - Assert.ThrowsException(() => serviceProvider.GetRequiredService()); - } - - [TestMethod] - public void TestAddUowAndUseSqlLite() - { - var options = new Mock(); - options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); - options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = options.Object.Services.BuildServiceProvider(); - Assert.IsNotNull(serviceProvider.GetRequiredService()); - } - - [TestMethod] - public void TestAddMultUow() - { - var options = new Mock(); - options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); - options.Object.UseUoW(options => options.UseSqlite(_connection)).UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = options.Object.Services.BuildServiceProvider(); - - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - } - - [TestMethod] - public async Task TestNoTransactionAndCommitAsync() - { - var serviceProviderAndDbContext = base.CreateDefault(); - var serviceProvider = serviceProviderAndDbContext.serviceProvider; - var dbContext = serviceProviderAndDbContext.dbContext; - - await using (var unitOfWork = serviceProvider.GetRequiredService()) - { - var transcation = unitOfWork.Transaction; - Assert.IsTrue(unitOfWork == serviceProvider.GetRequiredService().UnitOfWork); - - Users user = new Users() - { - Id = Guid.NewGuid(), - Name = Guid.NewGuid().ToString() - }; - dbContext.Add(user); - await unitOfWork.CommitAsync(); - - Assert.IsTrue(dbContext.User.Any(user => user.Id == user.Id)); - } - } - - [TestMethod] - public async Task TestUseTransactionAndCommitAsync() - { - var serviceProviderAndDbContext = base.CreateDefault(); - var serviceProvider = serviceProviderAndDbContext.serviceProvider; - var dbContext = serviceProviderAndDbContext.dbContext; - - using (var transcation = await dbContext.Database.BeginTransactionAsync()) - { - var unitOfWork = serviceProvider.GetRequiredService(); - - Users user = new Users() - { - Id = Guid.NewGuid(), - Name = Guid.NewGuid().ToString() - }; - dbContext.Add(user); - await unitOfWork.CommitAsync(); ; - } - } - - [TestMethod] - public async Task TestNoTransactionAsync() - { - var serviceProviderAndDbContext = base.CreateDefault(); - var serviceProvider = serviceProviderAndDbContext.serviceProvider; - var dbContext = serviceProviderAndDbContext.dbContext; - - await using (var unitOfWork = serviceProvider.GetRequiredService()) - { - Users user = new Users() - { - Id = Guid.NewGuid(), - Name = Guid.NewGuid().ToString().Substring(0, 6) - }; - dbContext.Add(user); - - await unitOfWork.SaveChangesAsync(); - - await Assert.ThrowsExceptionAsync(async () => - { - await unitOfWork.RollbackAsync(); - }); - - Assert.IsTrue(dbContext.User.Any(user => user.Id == user.Id)); - } - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Benchmarks.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs similarity index 89% rename from test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Benchmarks.cs rename to test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs index b65e87930..383306d65 100644 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Benchmarks.cs +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs @@ -1,4 +1,4 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest; +namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests; [SimpleJob(RunStrategy.ColdStart, RuntimeMoniker.Net60, targetCount: 100)] [MinColumn, MaxColumn, MeanColumn, MedianColumn] @@ -16,16 +16,16 @@ public void GlobalSetup() services.AddLogging(loggingBuilder => loggingBuilder.ClearProviders()); services.AddEventBus(); _serviceProvider = services.BuildServiceProvider(); - _eventBus = _serviceProvider.GetService(); + _eventBus = _serviceProvider.GetRequiredService(); _userEvent = new RegisterUserEvent() { Name = "tom", - Mobile = "18888888888" + PhoneNumber = "18888888888" }; _forgetPasswordEvent = new ForgetPasswordEvent() { Name = "lisa", - Mobile = "19999999999" + PhoneNumber = "19999999999" }; } diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/EventHandlers/CouponHandler.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs similarity index 90% rename from test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/EventHandlers/CouponHandler.cs rename to test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs index 691667eec..2d22773d0 100644 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/EventHandlers/CouponHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs @@ -1,8 +1,8 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.EventHandlers; +namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; public class CouponHandler { - private readonly ILogger _logger; + private readonly ILogger? _logger; public CouponHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/EventHandlers/NoticeHandler.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs similarity index 83% rename from test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/EventHandlers/NoticeHandler.cs rename to test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs index ea115ab65..001c4d74d 100644 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/EventHandlers/NoticeHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs @@ -1,8 +1,10 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.EventHandlers; +๏ปฟusing MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; + +namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; public class SendCouponHandler : ISagaEventHandler { - private readonly ILogger _logger; + private readonly ILogger? _logger; public SendCouponHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); @@ -22,7 +24,7 @@ public Task CancelAsync(ForgetPasswordEvent @event) public class NoticeSmsHandler : ISagaEventHandler { - private readonly ILogger _logger; + private readonly ILogger? _logger; public NoticeSmsHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); @@ -42,7 +44,7 @@ public Task CancelAsync(ForgetPasswordEvent @event) public class NoticeEmailHandler : ISagaEventHandler { - private readonly ILogger _logger; + private readonly ILogger? _logger; public NoticeEmailHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); @@ -58,4 +60,4 @@ public Task CancelAsync(ForgetPasswordEvent @event) _logger?.LogInformation("------Cancel Email Notice------"); return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs new file mode 100644 index 000000000..dc9e2fbb7 --- /dev/null +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; + +public record ForgetPasswordEvent : Event +{ + public string Name { get; set; } + + public string PhoneNumber { get; set; } +} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs new file mode 100644 index 000000000..30ba9d578 --- /dev/null +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; + +public record RegisterUserEvent : Event +{ + public string Name { get; set; } + + public string PhoneNumber { get; set; } +} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs new file mode 100644 index 000000000..fd07fde6c --- /dev/null +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs @@ -0,0 +1,15 @@ +๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Middleware; + +public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent +{ + private readonly ILogger>? _logger; + public LoggingMiddleware(ILogger>? logger = null) => _logger = logger; + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + var eventType = @event.GetType(); + _logger?.LogInformation("----- Handling command {CommandName} ({@Command})", eventType.FullName, @event); + + await next(); + } +} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.csproj b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj similarity index 85% rename from test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.csproj rename to test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj index a0945b095..0dbc15b6a 100644 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.csproj +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj @@ -2,20 +2,21 @@ Exe - net6.0 + net6.0 AnyCPU false enable false + enable - + - + diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Program.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs similarity index 83% rename from test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Program.cs rename to test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs index 0ec3ffd4c..0ec5f1cb3 100644 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Program.cs +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs @@ -1,4 +1,4 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest; +๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests; class Program { diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs similarity index 73% rename from test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/_Imports.cs rename to test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs index 4e7ce08eb..5276b15ee 100644 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs @@ -5,10 +5,11 @@ global using BenchmarkDotNet.Running; global using BenchmarkDotNet.Validators; global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.EventHandlers; -global using MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.Events; +global using MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; +global using MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; global using MASA.Contrib.Dispatcher.Events.Enums; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; global using System; global using System.Threading.Tasks; + diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/ForgetPasswordEvent.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/ForgetPasswordEvent.cs deleted file mode 100644 index d14180190..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/ForgetPasswordEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.Events; - -public record ForgetPasswordEvent : Event -{ - public string Name { get; set; } - - public string Mobile { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/RegisterUserEvent.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/RegisterUserEvent.cs deleted file mode 100644 index 10adbe699..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Events/RegisterUserEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.Events; - -public record RegisterUserEvent : Event -{ - public string Name { get; set; } - - public string Mobile { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Middleware/LoggingMiddleware.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Middleware/LoggingMiddleware.cs deleted file mode 100644 index 35b8394f6..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest/Extensions/Middleware/LoggingMiddleware.cs +++ /dev/null @@ -1,15 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest.Extensions.Middleware; - -public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent -{ - private readonly ILogger> _logger; - public LoggingMiddleware(ILogger> logger) => _logger = logger; - - public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) - { - var eventType = @event.GetType(); - _logger.LogInformation("----- Handling command {CommandName} ({@Command})", eventType.FullName, @event); - - await next(); - } -} \ No newline at end of file diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs index 178e4a9ba..200e042d0 100644 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs @@ -1,12 +1,10 @@ -using MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; - namespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.EventHandlers; public class AddGoodsHandler { [EventHandler] - public void AddGoods(AddGoodsEvent @event, ILogger logger) + public void AddGoods(AddGoodsEvent @event, ILogger? logger) { - logger.LogInformation($"add goods log,GoodsId:{@event.GoodsId},GoodsName:{@event.GoodsName},CategoryId:{@event.CategoryId}"); + logger?.LogInformation($"add goods log,GoodsId:{@event.GoodsId},GoodsName:{@event.GoodsName},CategoryId:{@event.CategoryId}"); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs index 15d889922..61d74bb3c 100644 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs @@ -1 +1,2 @@ +global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs index 17a1bf269..1681085fe 100644 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs @@ -1,5 +1,3 @@ -using MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; - namespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.EventHandlers; public class AddCatalogHandler diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs index e69de29bb..53869939c 100644 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs @@ -0,0 +1 @@ +global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs index 557f6d378..58b91c70c 100644 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs @@ -2,13 +2,13 @@ namespace MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.EventHandlers; public class AddBasketHandler { - private readonly ILogger _logger; - public AddBasketHandler(ILogger logger) => _logger = logger; + private readonly ILogger? _logger; + public AddBasketHandler(ILogger? logger) => _logger = logger; [EventHandler] public Task AddLog(AddBasketEvent @event) { - _logger.LogInformation($"add basket log๏ผšGoogdsId๏ผš{@event.GoodsId}๏ผŒcount๏ผš{@event.Count}"); + _logger?.LogInformation($"add basket log๏ผšGoogdsId๏ผš{@event.GoodsId}๏ผŒcount๏ผš{@event.Count}"); return Task.FromResult("success"); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs index b62ed164a..02ae7f52a 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs @@ -1,11 +1,9 @@ -using MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; - namespace MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.EventHandlers; public class UserEventHandler { [EventHandler(IsCancel = true)] - public void BindMobile(BindMobileEvent @event) + public void BindPhoneNumber(BindPhoneNumberEvent @event) { } diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindMobileEvent.cs b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs similarity index 58% rename from test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindMobileEvent.cs rename to test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs index e339a5215..a28c44bb8 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindMobileEvent.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs @@ -1,8 +1,8 @@ namespace MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; -public record BindMobileEvent : Event +public record BindPhoneNumberEvent : Event { public string AccountId { get; set; } - public string Mobile { get; set; } + public string PhoneNumber { get; set; } } diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj index 91a682f61..65fe8ba17 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj +++ b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj @@ -10,5 +10,5 @@ - + diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs index e69de29bb..fdc78d0c0 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs @@ -0,0 +1 @@ +global using MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs index 37677b5bd..1d0dcc2a3 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs @@ -1,23 +1,21 @@ -using MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; - namespace MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.EventHandlers; public class EditCategoryHandler : ISagaEventHandler { - private readonly ILogger _logger; - public EditCategoryHandler(ILogger logger) => _logger = logger; + private readonly ILogger? _logger; + public EditCategoryHandler(ILogger? logger = null) => _logger = logger; [EventHandler(10)] public Task CancelAsync(EditCategoryEvent @event) { - _logger.LogInformation($"cancel edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); + _logger?.LogInformation($"cancel edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); return Task.CompletedTask; } [EventHandler(20)] public Task HandleAsync(EditCategoryEvent @event) { - _logger.LogInformation($"edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); + _logger?.LogInformation($"edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); return Task.CompletedTask; } } diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj index 91a682f61..65fe8ba17 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj +++ b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj @@ -10,5 +10,5 @@ - + diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs index 7c7bad467..cecf0b334 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs @@ -1,2 +1,3 @@ global using MASA.BuildingBlocks.Dispatcher.Events; +global using MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs index 31565057f..a97d6302a 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs @@ -1,16 +1,14 @@ -๏ปฟusing MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; - namespace MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.EventHandlers; public class OrderStockConfirmedHandler { - private readonly ILogger _logger; + private readonly ILogger? _logger; - public OrderStockConfirmedHandler(ILogger logger) => _logger = logger; + public OrderStockConfirmedHandler(ILogger? logger = null) => _logger = logger; [EventHandler(-10)] public void AddLog(OrderStockConfirmedEvent @event) { - _logger.LogInformation($"add order stock confirmed log,orderId:{@event.OrderId}"); + _logger?.LogInformation($"add order stock confirmed log,orderId:{@event.OrderId}"); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj index 91a682f61..65fe8ba17 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj +++ b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj @@ -10,5 +10,5 @@ - + diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs index 15d889922..abbead4e1 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs @@ -1 +1,2 @@ +global using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs index e4256e6b8..ca832f1f9 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs @@ -1,16 +1,14 @@ -using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; - namespace MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.EventHandlers; public class EditGoodsHandler : IEventHandler { - private readonly ILogger _logger; - public EditGoodsHandler(ILogger logger) => _logger = logger; + private readonly ILogger? _logger; + public EditGoodsHandler(ILogger? logger) => _logger = logger; [EventHandler(-10)] public Task HandleAsync(EditGoodsEvent @event) { - _logger.LogInformation($"edit goods log๏ผŒGoodsId:{@event.GoodsId},Name:{@event.GoodsName},CategoryId:{@event.CategoryId}"); + _logger?.LogInformation($"edit goods log๏ผŒGoodsId:{@event.GoodsId},Name:{@event.GoodsName},CategoryId:{@event.CategoryId}"); return Task.CompletedTask; } } diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs index 7c7bad467..2fa58832a 100644 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs @@ -1,2 +1,3 @@ global using MASA.BuildingBlocks.Dispatcher.Events; +global using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs index d0a8c6f1a..c9b51ff42 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs @@ -33,7 +33,7 @@ public void TestAddNullAssembly() services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); Assert.ThrowsException(() => { - services.AddEventBus(options => options.Assemblies = null); + services.AddEventBus(options => options.Assemblies = null!); }); } @@ -57,7 +57,7 @@ public void TestEventBusByAddNullAssembly() services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); Assert.ThrowsException(() => { - services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = null); + services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = null!); }); } @@ -116,7 +116,7 @@ public void TestAddMultEventBus() [TestMethod] public void TestUseEventBusAndNullServices() { - var options = new DispatcherOptions(null); + var options = new DispatcherOptions(null!); Assert.ThrowsException(() => options.UseEventBus()); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs index 6ed94ada0..6a28ce658 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs @@ -1,4 +1,4 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests; +namespace MASA.Contrib.Dispatcher.Events.Tests; [TestClass] public class ChoreTest : TestBase @@ -6,7 +6,7 @@ public class ChoreTest : TestBase private readonly IEventBus _eventBus; public ChoreTest() { - _eventBus = _serviceProvider.GetService(); + _eventBus = _serviceProvider.GetRequiredService(); } [DataTestMethod] @@ -99,4 +99,4 @@ public void TestDispatchHandlerConstructor() Assert.IsTrue(dispatchHandler.RetryTimes.Equals(5)); Assert.IsTrue(dispatchHandler.IsCancel.Equals(true)); } -} \ No newline at end of file +} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs index 429cd79f7..202fdc8bd 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs @@ -2,8 +2,8 @@ namespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; public class ChangePasswordEventHandler : ISagaEventHandler { - private readonly ILogger _logger; - public ChangePasswordEventHandler(ILogger logger) => _logger = logger; + private readonly ILogger? _logger; + public ChangePasswordEventHandler(ILogger? logger=null) => _logger = logger; [EventHandler(10, FailureLevels.ThrowAndCancel)] public Task HandleAsync(ChangePasswordEvent @event) @@ -21,7 +21,7 @@ public Task CancelAsync(ChangePasswordEvent @event) { throw new ArgumentException("System error, please try again later"); } - _logger.LogInformation("cancel success"); + _logger?.LogInformation("cancel success"); return Task.CompletedTask; } @@ -33,7 +33,7 @@ public Task AddCancelLogs(ChangePasswordEvent @event) { throw new ArgumentException("System error, please try again later"); } - _logger.LogInformation("cancel success"); + _logger?.LogInformation("cancel success"); return Task.CompletedTask; } } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs index 98bb34842..c3400a461 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs @@ -4,9 +4,9 @@ public class ShipOrderEventHandler : ISagaEventHandler { private int ExecCount { get; set; } - private readonly ILogger _logger; + private readonly ILogger? _logger; - public ShipOrderEventHandler(ILogger logger) + public ShipOrderEventHandler(ILogger? logger = null) { _logger = logger; ExecCount = 0; @@ -21,7 +21,7 @@ public Task HandleAsync(ShipOrderEvent @event) throw new Exception("try again"); } - _logger.LogInformation("update express information"); + _logger?.LogInformation("update express information"); if (@event.OrderId.Length > 8) { @event.Message = "the delivery failure"; @@ -35,21 +35,21 @@ public Task HandleAsync(ShipOrderEvent @event) public Task CancelAsync(ShipOrderEvent @event) { @event.Message = "the delivery failed, rolling back success"; - _logger.LogInformation("the delivery failed, rolling back success"); + _logger?.LogInformation("the delivery failed, rolling back success"); return Task.CompletedTask; } } public class ShipOrderAndNoticeHandler : IEventHandler { - private readonly ILogger _logger; - public ShipOrderAndNoticeHandler(ILogger logger) => _logger = logger; + private readonly ILogger? _logger; + public ShipOrderAndNoticeHandler(ILogger? logger = null) => _logger = logger; [EventHandler(20)] public Task HandleAsync(ShipOrderEvent @event) { @event.Message = "the delivery and notice success"; - _logger.LogInformation("order delivered successfully"); + _logger?.LogInformation("order delivered successfully"); return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs index 34ed72a71..0b41eb4b8 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs @@ -4,10 +4,10 @@ public class TransferEventHandler : ISagaEventHandler { private readonly List _blackAccount = new List() { "roller", "thomas" }; - private readonly ILogger _logger; + private readonly ILogger? _logger; private readonly IEventBus _eventBus; - public TransferEventHandler(ILogger logger, IEventBus eventBus) + public TransferEventHandler(IEventBus eventBus, ILogger? logger = null) { _logger = logger; _eventBus = eventBus; @@ -20,7 +20,7 @@ public Task HandleAsync(TransferEvent @event) { throw new NotSupportedException("System error, please try again later"); } - _logger.LogInformation("deduct account balance {event}", @event.ToString()); + _logger?.LogInformation("deduct account balance {event}", @event.ToString()); return Task.CompletedTask; } @@ -45,7 +45,7 @@ public async Task DeductionMoneyHandler(DeductionMoneyEvent @event) IncreaseMoneyEvent increaseMoneyEvent = new IncreaseMoneyEvent() { Account = @event.PayeeAccount, - TransferAccount=@event.Account, + TransferAccount = @event.Account, Money = @event.Money }; await _eventBus.PublishAsync(increaseMoneyEvent); diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs index d36975649..9623b3fc3 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs @@ -2,7 +2,7 @@ namespace MASA.Contrib.Dispatcher.Events.Tests.Events; public record DeductionMoneyEvent : Event, ITransaction { - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } public string Account { get; set; } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs index 62ed400e9..ecbe79664 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs @@ -2,7 +2,7 @@ namespace MASA.Contrib.Dispatcher.Events.Tests.Events; public record IncreaseMoneyEvent : Event, ITransaction { - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } public string Account { get; set; } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs index 814545634..c715ad3ce 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs @@ -8,7 +8,7 @@ public class OrderPaymentFailedIntegrationEvent : IIntegrationEvent public string Topic { get; set; } = nameof(OrderPaymentFailedIntegrationEvent); - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } public string OrderId { get; set; } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs index ea3419e56..0a6824ab8 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs @@ -1,5 +1,3 @@ -using Moq; - namespace MASA.Contrib.Dispatcher.Events.Tests; [TestClass] @@ -8,7 +6,7 @@ public class FeaturesTest : TestBase private readonly IEventBus _eventBus; public FeaturesTest() : base() { - _eventBus = _serviceProvider.GetService(); + _eventBus = _serviceProvider.GetRequiredService(); } [TestMethod] @@ -79,8 +77,8 @@ public async Task TestCorrectEventBus() [TestMethod] public async Task TestNullEvent() { - AddShoppingCartEvent @event = null; - await Assert.ThrowsExceptionAsync(async () => await _eventBus.PublishAsync(@event)); + AddShoppingCartEvent? @event = null; + await Assert.ThrowsExceptionAsync(async () => await _eventBus.PublishAsync(@event!)); } [DataTestMethod] @@ -100,10 +98,6 @@ public async Task TestMultiHandler(string price, int count, string discountAmoun [TestMethod] public async Task TestNotParameter() { - var @event = new DeleteGoodsEvent() - { - CreationTime = DateTime.Now, - }; await Assert.ThrowsExceptionAsync(async () => { try @@ -148,7 +142,7 @@ public Task TestOrderLessThenZero() { ResetMemoryEventBus(typeof(FeaturesTest).Assembly); } - catch (Exception ex) + catch (Exception) { } @@ -165,7 +159,7 @@ public Task TestOnlyCancelHandler() { try { - ResetMemoryEventBus(typeof(OnlyCancelHandler.Tests.Events.BindMobileEvent).Assembly); + ResetMemoryEventBus(typeof(OnlyCancelHandler.Tests.Events.BindPhoneNumberEvent).Assembly); } catch (NotSupportedException) { @@ -224,10 +218,10 @@ public async Task TestTransferEventAndOpenTransaction() { base.ResetMemoryEventBus(services => { - var unitOfWork = new Mock(); - unitOfWork.Setup(x => x.TransactionHasBegun).Returns(true); - unitOfWork.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); - services.AddScoped(serviceProvider => unitOfWork.Object); + var uoW = new Mock(); + uoW.Setup(x => x.TransactionHasBegun).Returns(true); + uoW.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); + services.AddScoped(serviceProvider => uoW.Object); return services; }, true, typeof(AssemblyResolutionTests).Assembly); var @event = new DeductionMoneyEvent() @@ -240,13 +234,10 @@ public async Task TestTransferEventAndOpenTransaction() } [TestMethod] - public async Task TestTransferEventAndCloseTransaction() + public async Task TestCommitAsync() { base.ResetMemoryEventBus(services => { - var unitOfWork = new Mock(); - unitOfWork.Setup(e => e.SaveChangesAsync(CancellationToken.None)).Verifiable(); - services.AddScoped(serviceProvider => unitOfWork.Object); return services; }, true, typeof(AssemblyResolutionTests).Assembly); var @event = new DeductionMoneyEvent() @@ -255,19 +246,20 @@ public async Task TestTransferEventAndCloseTransaction() PayeeAccount = "Jim", Money = 100 }; - await _services.BuildServiceProvider().GetRequiredService().PublishAsync(@event); + var serviceProvider = _services.BuildServiceProvider(); + var eventBus = serviceProvider.GetRequiredService(); + + await Assert.ThrowsExceptionAsync(async () => await eventBus.CommitAsync(default)); } [TestMethod] - public async Task TestTransferEventAndOpenTransactionRollback() + public async Task TestUseUoWCommitAsync() { + var uoW = new Mock(); base.ResetMemoryEventBus(services => { - var unitOfWork = new Mock(); - unitOfWork.Setup(x => x.TransactionHasBegun).Returns(true); - unitOfWork.Setup(e => e.CommitAsync(CancellationToken.None)).Throws(new ArgumentOutOfRangeException("The Money is error")); - unitOfWork.Setup(e => e.RollbackAsync(CancellationToken.None)).Verifiable(); - services.AddScoped(serviceProvider => unitOfWork.Object); + uoW.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); + services.AddScoped(serviceProvider => uoW.Object); return services; }, true, typeof(AssemblyResolutionTests).Assembly); var @event = new DeductionMoneyEvent() @@ -276,7 +268,11 @@ public async Task TestTransferEventAndOpenTransactionRollback() PayeeAccount = "Jim", Money = 100 }; + var serviceProvider = _services.BuildServiceProvider(); + var eventBus = serviceProvider.GetRequiredService(); + await eventBus.PublishAsync(@event); - await _services.BuildServiceProvider().GetRequiredService().PublishAsync(@event); + await eventBus.CommitAsync(default); + uoW.Verify(u => u.CommitAsync(default), Times.Once); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj index 38c8d126a..3470232d9 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj @@ -3,6 +3,7 @@ net6.0 false + enable Full enable @@ -12,13 +13,17 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs index 454460825..e3088c8ab 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs @@ -2,13 +2,13 @@ namespace MASA.Contrib.Dispatcher.Events.Tests.Middleware; public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent { - private readonly ILogger> _logger; - public LoggingMiddleware(ILogger> logger) => _logger = logger; + private readonly ILogger>? _logger; + public LoggingMiddleware(ILogger>? logger = null) => _logger = logger; public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) { var eventType = @event.GetType(); - _logger.LogInformation("----- Handling command {FullName} ({event})", eventType.FullName, @event); + _logger?.LogInformation("----- Handling command {FullName} ({event})", eventType.FullName, @event); await next(); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs index 1880990d3..ce8fee8b2 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs @@ -6,7 +6,7 @@ public class SagaTest : TestBase private readonly IEventBus _eventBus; public SagaTest() : base() { - _eventBus = _serviceProvider.GetService(); + _eventBus = _serviceProvider.GetRequiredService(); } [DataTestMethod] @@ -30,7 +30,7 @@ public async Task TestExecuteAbnormalExit(string orderId, string orderState, str [DataRow("jordan", "change password notcices @", 0)] public async Task TestLastCancelError(string account, string content, int isError) { - ResetMemoryEventBus(false, null); + ResetMemoryEventBus(false, null!); ChangePasswordEvent @event = new ChangePasswordEvent() { Account = account, @@ -89,7 +89,7 @@ await Assert.ThrowsExceptionAsync(async () => [TestMethod] public async Task TestMultiOrderBySaga() { - IEventBus eventBus = null; + IEventBus? eventBus = null; Assert.ThrowsException(() => { ResetMemoryEventBus(false, typeof(SagaTest).Assembly, typeof(EditCategoryEvent).Assembly); @@ -104,13 +104,13 @@ public async Task TestMultiOrderBySaga() { await eventBus.PublishAsync(@event); } - ResetMemoryEventBus(false, null); + ResetMemoryEventBus(false, null!); } [TestMethod] public async Task TestLessThenZeroBySaga() { - IEventBus eventBus = null; + IEventBus? eventBus = null; Assert.ThrowsException(() => { ResetMemoryEventBus(false, typeof(EditGoodsEvent).Assembly); @@ -126,6 +126,6 @@ public async Task TestLessThenZeroBySaga() { await eventBus.PublishAsync(@event); } - ResetMemoryEventBus(false, null); + ResetMemoryEventBus(false, null!); } } diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs index 4d013922c..551056db7 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs @@ -12,9 +12,9 @@ public TestBase() : this(null) } - public TestBase(Func func = null) => ResetMemoryEventBus(func, false, null); + public TestBase(Func? func = null) => ResetMemoryEventBus(func, false, null); - protected void ResetMemoryEventBus(Func func = null, bool isAddLog = true, params Assembly[] assemblies) + protected void ResetMemoryEventBus(Func? func = null, bool isAddLog = true, params Assembly[]? assemblies) { _services = new ServiceCollection(); if (isAddLog) diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs index 506c0cf2d..dda486fce 100644 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs @@ -1,4 +1,4 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; @@ -16,4 +16,5 @@ global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; global using System.Reflection; diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/ForgetPasswordEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/ForgetPasswordEvent.cs deleted file mode 100644 index b64b74881..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/ForgetPasswordEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests.Events; - -public record ForgetPasswordEvent : IntegrationEvent -{ - public override string Topic { get; set; } = nameof(ForgetPasswordEvent); - - public string Account { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs index 9ba051162..d1f551f25 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs @@ -2,6 +2,16 @@ namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests.Events; public record RegisterUserIntegrationEvent : IntegrationEvent { + public RegisterUserIntegrationEvent() + { + + } + + public RegisterUserIntegrationEvent(Guid id, DateTime creationTime) : base(id, creationTime) + { + + } + public string Account { get; set; } public string Password { get; set; } diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs index 1376996a7..9bd6b68d4 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs @@ -1,199 +1,338 @@ -using MASA.Utils.Models.Config; -using Microsoft.Extensions.Options; - namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests; [TestClass] -public class IntegrationEventBusTest : TestBase +public class IntegrationEventBusTest { - [TestMethod] - public async Task TestPublishSuccessAsync() + private Mock _options; + private Mock> _dispatcherOptions; + private Mock _daprClient; + private Mock> _logger; + private Mock _eventLog; + private Mock> _appConfig; + private Mock _eventBus; + private Mock _uoW; + + [TestInitialize] + public void Initialize() { - var serviceProvider = CreateDefaultProvider("RegisterUser"); - var eventBus = serviceProvider.GetRequiredService(); - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + _options = new(); + _options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); + _dispatcherOptions = new(); + _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services)); + _daprClient = new(); + _logger = new(); + _eventLog = new(); + _eventLog.Setup(eventLog => eventLog.SaveEventAsync(It.IsAny(), null!)).Verifiable(); + _eventLog.Setup(eventLog => eventLog.MarkEventAsInProgressAsync(It.IsAny())).Verifiable(); + _eventLog.Setup(eventLog => eventLog.MarkEventAsPublishedAsync(It.IsAny())).Verifiable(); + _eventLog.Setup(eventLog => eventLog.MarkEventAsFailedAsync(It.IsAny())).Verifiable(); + _appConfig = new(); + _appConfig.Setup(appConfig => appConfig.CurrentValue).Returns(() => new AppConfig() { - Account = "lisa", - Password = "123456" - }; - await eventBus.PublishAsync(@event); + AppId = "Test" + }); + _eventBus = new(); + _uoW = new(); + _uoW.Setup(uoW => uoW.CommitAsync(default)).Verifiable(); + _uoW.Setup(uoW => uoW.Transaction).Returns(() => null!); } [TestMethod] - public void TestAddMultDaprEventBusAsync() + public void TestDispatcherOption() { - var options = new DispatcherOptions(new ServiceCollection()) + var services = new ServiceCollection(); + DispatcherOptions options; + + Assert.ThrowsException(() => { - Assemblies = AppDomain.CurrentDomain.GetAssemblies() + options = new DispatcherOptions(services) + { + Assemblies = null! + }; + }); + Assert.ThrowsException(() => + { + options = new DispatcherOptions(services) + { + Assemblies = new System.Reflection.Assembly[0] + }; + }); + options = new DispatcherOptions(services) + { + Assemblies = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } }; - options.UseDaprEventBus() + Assert.IsTrue(options.Services.Equals(services)); + var allEventTypes = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && type != typeof(IntegrationEvent) && typeof(IEvent).IsAssignableFrom(type)).ToList(); + Assert.IsTrue(options.AllEventTypes.Count == allEventTypes.Count()); + + } + + [TestMethod] + public void TestAddMultDaprEventBus() + { + _dispatcherOptions.Object.Value.UseDaprEventBus() .UseDaprEventBus(); - var serviceProvider = options.Services.BuildServiceProvider(); + var serviceProvider = _dispatcherOptions.Object.Value.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); } [TestMethod] - public void TestAddDaprEventBusAndNullServicesAsync() + public void TestAddDaprEventBus() { - var options = new DispatcherOptions(null); - var ex = Assert.ThrowsException(() => options.UseDaprEventBus()); - Assert.IsTrue(ex.Message == $"Value cannot be null. (Parameter '{nameof(options.Services)}')"); + IServiceCollection services = new ServiceCollection(); + services.AddDaprEventBus(); + var serviceProvider = services.BuildServiceProvider(); + var integrationEventBus = serviceProvider.GetRequiredService(); + Assert.IsNotNull(integrationEventBus); } [TestMethod] - public void TestAddDaprEventBusAndNullAssemblyAsync() + public void TestEmptyPubSub() { - Assert.ThrowsException(() => new DispatcherOptions(new ServiceCollection()) + IServiceCollection services = new ServiceCollection(); + Assert.ThrowsException(() => { - Assemblies = null + services.AddDaprEventBus(option => + { + option.PubSubName = ""; + }); }); } [TestMethod] - public async Task TestPublishAsync() + public void TestAddDaprEventBusAndChangeAssemblies() { - var services = new ServiceCollection(); - Mock unitWork = new(); - Mock dbTransaction = new(); - unitWork.Setup(u => u.Transaction).Returns(dbTransaction.Object); - services.AddScoped((serviceProvider) => unitWork.Object); - services.AddOptions(); - services.AddLogging(); - services.AddDaprEventBus(options => + IServiceCollection services = new ServiceCollection(); + + services.AddDaprEventBus(option => { + option.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + option.PubSubName = "pubsub"; }); - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() - { - Account = "lisa", - Password = "123456" - }; var serviceProvider = services.BuildServiceProvider(); var integrationEventBus = serviceProvider.GetRequiredService(); - await integrationEventBus.PublishAsync(@event); + Assert.IsNotNull(integrationEventBus); + } - Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == 3); + [TestMethod] + public void TestAddDaprEventBusAndNullServicesAsync() + { + _options.Setup(option => option.Services).Returns(() => null!); + var ex = Assert.ThrowsException(() => _options.Object.UseDaprEventBus()); + Assert.IsTrue(ex.Message == $"Value cannot be null. (Parameter '{nameof(_options.Object.Services)}')"); } [TestMethod] - public async Task TestPublishFailedAsync() + public async Task TestPublishIntegrationEventAsync() { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() { Account = "lisa", Password = "123456" }; - var serviceProvider = CreateCustomerDaprPubSubProvider(DAPR_PUBSUB_NAME, (services) => - { - Mock daprClient = new(); - daprClient.Setup(e => e.PublishEventAsync(DAPR_PUBSUB_NAME, @event.Topic, It.IsAny(), default)) - .Throws(new Exception("send failure")); - services.AddSingleton(_ => daprClient.Object); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - }); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); + _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); + await integrationEventBus.PublishAsync(@event); + + _daprClient.Verify(dapr => dapr.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); } [TestMethod] - public async Task TestDbTransactionPublishSuccessAsync() + public async Task TestPublishIntegrationEventAndFailedAsync() { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() { Account = "lisa", Password = "123456" }; - var serviceProvider = CreateDefaultProvider(@event.Topic); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); + _eventLog.Setup(eventLog => eventLog.MarkEventAsPublishedAsync(It.IsAny())).Throws(); + _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); + await integrationEventBus.PublishAsync(@event); + + _eventLog.Verify(eventLog => eventLog.MarkEventAsInProgressAsync(@event.Id), Times.Once); + _daprClient.Verify(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); + _eventLog.Verify(eventLog => eventLog.MarkEventAsPublishedAsync(@event.Id), Times.Once); + _eventLog.Verify(eventLog => eventLog.MarkEventAsFailedAsync(@event.Id), Times.Once); } [TestMethod] - public async Task TestDbTransactionPublishFailedAsync() + public async Task TestPublishIntegrationEventAndNotUoWAsync() { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() { Account = "lisa", - Password = "123456" + Password = "123456", + UnitOfWork = _uoW.Object }; - var serviceProvider = CreateCustomerDaprPubSubProvider(DAPR_PUBSUB_NAME, (services) => - { - Mock daprClient = new(); - daprClient.Setup(e => e.PublishEventAsync(DAPR_PUBSUB_NAME, @event.Topic, It.IsAny(), default)) - .Throws(new Exception("send failure")); - services.AddSingleton(_ => daprClient.Object); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - }); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); + _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); + await integrationEventBus.PublishAsync(@event); + + _daprClient.Verify(dapr => dapr.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); } [TestMethod] - public async Task CheckCustomerPubSubName() + public async Task TestPublishEventAsync() { - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); + CreateUserEvent @event = new CreateUserEvent() { - Account = "lisa", - Password = "123456" + Name = "Tom" }; - var daprPubSubName = "PUBSUB"; - var serviceProvider = CreateCustomerDaprPubSubProvider(daprPubSubName, @event.Topic); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); - } + await integrationEventBus.PublishAsync(@event); + _eventBus.Verify(eventBus => eventBus.PublishAsync(It.IsAny()), Times.Once); + } [TestMethod] - public async Task CheckPublishEvent() + public async Task TestPublishEventAndNotEventBusAsync() { - ForgetPasswordEvent @event = new ForgetPasswordEvent() + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + null, + _uoW.Object); + CreateUserEvent @event = new CreateUserEvent() { - Account = "lisa" + Name = "Tom" }; - var daprPubSubName = "PUBSUB"; - var serviceProvider = CreateCustomerDaprPubSubProvider(daprPubSubName, ""); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); + await Assert.ThrowsExceptionAsync(async () => + { + await integrationEventBus.PublishAsync(@event); + }); } [TestMethod] - public async Task TestPublishEventAndNotUseEventBusAsync() + public async Task TestCommitAsync() { - IOptions options = Options.Create(new DispatcherOptions(new ServiceCollection())); - Mock client = new(); - Mock eventLogService = new(); - Mock> appConfig = new(); - Mock> logger = new(); + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); - var integrationEventBus = new IntegrationEventBus(options, client.Object, eventLogService.Object, appConfig.Object, logger.Object); - var @event = new CreateUserEvent("tom"); - await Assert.ThrowsExceptionAsync(async () => await integrationEventBus.PublishAsync(@event)); + await integrationEventBus.CommitAsync(default); + _uoW.Verify(uoW => uoW.CommitAsync(default), Times.Once); } [TestMethod] - public async Task TestPublishEventAsync() + public async Task TestNotUseUowCommitAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + null); + + await Assert.ThrowsExceptionAsync(async () => await integrationEventBus.CommitAsync()); + } + + [TestMethod] + public void TestGetAllEventTypes() { - IOptions options = Options.Create(new DispatcherOptions(new ServiceCollection()) + _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services) { - Assemblies = AppDomain.CurrentDomain.GetAssemblies() + Assemblies = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } }); - Mock client = new(); - Mock eventLogService = new(); - Mock> appConfig = new(); - Mock> logger = new(); - - Mock eventBus = new(); - eventBus.Setup(e => e.PublishAsync(It.IsAny())).Verifiable(); - eventBus.Setup(e => e.GetAllEventTypes()).Returns(() => new List() - { - typeof(CreateUserEvent), - typeof(ForgetPasswordEvent), - typeof(RegisterUserIntegrationEvent) + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + null, + null); + + Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == _dispatcherOptions.Object.Value.AllEventTypes.Count()); + } + + + [TestMethod] + public void TestUseEventBusGetAllEventTypes() + { + var defaultAssembly = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly }; + _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services) + { + Assemblies = defaultAssembly }); + var allEventType = defaultAssembly + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) + .ToList(); + _eventBus.Setup(eventBus => eventBus.GetAllEventTypes()).Returns(() => allEventType).Verifiable(); + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + null); - var integrationEventBus = new IntegrationEventBus(options, client.Object, eventLogService.Object, appConfig.Object, logger.Object, eventBus.Object); - var @event = new CreateUserEvent("tom"); - await integrationEventBus.PublishAsync(@event); + Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == _dispatcherOptions.Object.Value.AllEventTypes.Count()); + Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == allEventType.Count()); + } + + [TestMethod] + public void TestIntegrationEvent() + { + DateTime date = DateTime.UtcNow; + Guid id = Guid.NewGuid(); + RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + { + Account = "lisa", + Password = "123456" + }; + Assert.IsTrue(@event.CreationTime > date); + Assert.IsTrue(@event.Id != default(Guid)); - Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == 3); + @event = new RegisterUserIntegrationEvent(id, date) + { + Account = "lisa", + Password = "123456" + }; + Assert.IsTrue(@event.CreationTime == date); + Assert.IsTrue(@event.Id == id); } public class IntegrationEventLogService : IIntegrationEventLogService @@ -203,6 +342,11 @@ public Task MarkEventAsFailedAsync(Guid eventId) return Task.CompletedTask; } + public Task DeleteExpiresAsync(DateTime expiresAt, int batchCount = 1000, CancellationToken token = new CancellationToken()) + { + throw new NotImplementedException(); + } + public Task MarkEventAsInProgressAsync(Guid eventId) { return Task.CompletedTask; @@ -213,9 +357,9 @@ public Task MarkEventAsPublishedAsync(Guid eventId) return Task.CompletedTask; } - public async Task> RetrieveEventLogsPendingToPublishAsync(Guid transactionId) + public Task> RetrieveEventLogsFailedToPublishAsync(int retryBatchSize = 200, int maxRetryTimes = 10, int minimumRetryInterval = 60) { - return await Task.FromResult(new List()); + return Task.FromResult(new List().AsEnumerable()); } public Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj index fbaf82443..cedee4cdf 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj @@ -9,11 +9,15 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/RegisterServicesBusTest.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/RegisterServicesBusTest.cs deleted file mode 100644 index 8925a032c..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/RegisterServicesBusTest.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests; - -[TestClass] -public class RegisterServicesBusTest : TestBase -{ - [TestMethod] - public void TestEmptyDaprPubSubName() - { - var daprPubSubName = string.Empty; - Assert.ThrowsException(() => - { - var serviceProvider = CreateCustomerDaprPubSubProvider(daprPubSubName, "topic"); - }); - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/TestBase.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/TestBase.cs deleted file mode 100644 index 8c6a8dd03..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/TestBase.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests; - -[TestClass] -public class TestBase -{ - protected const string DAPR_PUBSUB_NAME = "pubsub"; - - protected IServiceProvider CreateDefaultProvider(string topic, Action? action = null) - { - return CreateProvider(services => - { - action?.Invoke(services); - Mock daprClient = new(); - daprClient.Setup(e => e.PublishEventAsync(DAPR_PUBSUB_NAME, topic, It.IsAny(), default)).Verifiable(); - services.AddSingleton(_ => daprClient.Object); - - services.AddDaprEventBus(); - }); - } - - protected IServiceProvider CreateCustomerDaprPubSubProvider(string daprPubSubName, string topic, Action? action = null) - { - return CreateCustomerDaprPubSubProvider(daprPubSubName, (services) => - { - action?.Invoke(services); - Mock daprClient = new(); - daprClient.Setup(e => e.PublishEventAsync(daprPubSubName, topic, It.IsAny(), default)).Verifiable(); - services.AddSingleton(_ => daprClient.Object); - }); - } - - protected IServiceProvider CreateCustomerDaprPubSubProvider(string daprPubSubName, Action? action = null) - { - return CreateProvider(services => - { - action?.Invoke(services); - services.AddDaprEventBus(options => options.PubSubName = daprPubSubName); - }); - } - - private IServiceProvider CreateProvider(Action? action = null) - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - - Mock eventBus = new(); - eventBus.Setup(e => e.PublishAsync(It.IsAny())).Verifiable(); - services.AddScoped((serviceProvider) => eventBus.Object); - - Mock unitWork = new(); - Mock dbTransaction = new(); - unitWork.Setup(u => u.Transaction).Returns(dbTransaction.Object); - services.AddScoped((serviceProvider) => unitWork.Object); - - action?.Invoke(services); - return services.BuildServiceProvider(); - } -} - -public class IntegrationEventLogService : IIntegrationEventLogService -{ - public Task MarkEventAsFailedAsync(Guid eventId) - { - return Task.CompletedTask; - } - - public Task MarkEventAsInProgressAsync(Guid eventId) - { - return Task.CompletedTask; - } - - public Task MarkEventAsPublishedAsync(Guid eventId) - { - return Task.CompletedTask; - } - - public Task> RetrieveEventLogsPendingToPublishAsync(Guid transactionId) - { - return Task.FromResult(new List().AsEnumerable()); - } - - public Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) - { - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs index 0da27f506..d2b2f3b43 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs @@ -1,5 +1,5 @@ global using Dapr.Client; -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; @@ -7,8 +7,10 @@ global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.Events; global using MASA.Contrib.Dispatcher.IntegrationEvents.Tests.Events; +global using MASA.Utils.Models.Config; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; global using System.Data.Common; diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs index 32fc849e8..445a38cec 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs @@ -7,7 +7,7 @@ public abstract record IntegrationEvent : IIntegrationEvent public DateTime CreationTime { get; init; } [JsonIgnore] - public IUnitOfWork UnitOfWork { get; set; } + public IUnitOfWork? UnitOfWork { get; set; } public abstract string Topic { get; set; } diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs index f5d6b64a0..9931bd08a 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs @@ -1,6 +1,3 @@ -using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Infrastructure; -using Moq; - namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; [TestClass] @@ -13,7 +10,7 @@ public async Task TestNullDbTransactionAsync() var @event = new OrderPaymentSucceededIntegrationEvent() { OrderId = "1234567890123", - PaymentTime = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds }; var serviceProvider = CreateDefaultProvider(); var dbContext = serviceProvider.GetRequiredService(); @@ -21,57 +18,6 @@ public async Task TestNullDbTransactionAsync() await Assert.ThrowsExceptionAsync(async () => await eventLogService.SaveEventAsync(@event, transaction)); } - [TestMethod] - public async Task TestEventLogServiceAsync() - { - var serviceProvider = CreateDefaultProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var transaction = dbContext.Database.GetDbConnection().BeginTransaction(); - var @event = new OrderPaymentSucceededIntegrationEvent() - { - OrderId = "1234567890123", - PaymentTime = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds - }; - - var eventLogService = serviceProvider.GetRequiredService(); - await eventLogService.SaveEventAsync(@event, transaction); - - var transactionId = dbContext.Database.CurrentTransaction!.TransactionId; - - var eventLog = dbContext.EventLogs.FirstOrDefault(); - Assert.IsNotNull(eventLog); - Assert.IsTrue(eventLog.State == IntegrationEventStates.NotPublished); - Assert.IsTrue(eventLog.Id == @event.Id); - - var eventLogs = await eventLogService.RetrieveEventLogsPendingToPublishAsync(transactionId); - Assert.IsNotNull(eventLogs.Count() == 1); - eventLog = dbContext.EventLogs.FirstOrDefault(); - Assert.IsNotNull(eventLog); - Assert.IsTrue(eventLog.State == IntegrationEventStates.NotPublished); - Assert.IsTrue(eventLog.Id == @event.Id); - - - await eventLogService.MarkEventAsInProgressAsync(eventLog.Id); - eventLog = dbContext.EventLogs.Where(x => x.Id == eventLog.Id).FirstOrDefault(); - Assert.IsNotNull(eventLog); - Assert.IsTrue(eventLog.State == IntegrationEventStates.InProgress); - Assert.IsTrue(eventLog.TimesSent == 1); - - await eventLogService.MarkEventAsPublishedAsync(eventLog.Id); - eventLog = dbContext.EventLogs.Where(x => x.Id == eventLog.Id).FirstOrDefault(); - Assert.IsNotNull(eventLog); - Assert.IsTrue(eventLog.State == IntegrationEventStates.Published); - - await eventLogService.MarkEventAsFailedAsync(eventLog.Id); - eventLog = dbContext.EventLogs.Where(x => x.Id == eventLog.Id).FirstOrDefault(); - Assert.IsNotNull(eventLog); - Assert.IsTrue(eventLog.State == IntegrationEventStates.PublishedFailed); - - eventLogs = await eventLogService.RetrieveEventLogsPendingToPublishAsync(transactionId); - Assert.IsNotNull(eventLogs.Count() == 0); - } - [TestMethod] public void TestMultUseEventLogService() { @@ -85,30 +31,21 @@ public void TestMultUseEventLogService() [TestMethod] public void TestNullServices() { - var options = new DispatcherOptions(null); - Assert.ThrowsException(() => - { - options.UseEventLog(options => - { - options.UseSqlite(base._connection); - }); - }); + var options = new DispatcherOptions(null!); + Assert.ThrowsException(() => { options.UseEventLog(options => { options.UseSqlite(base._connection); }); }); } [TestMethod] public void TestNullDbContextOptionsBuilder() { var options = new DispatcherOptions(new ServiceCollection()); - Assert.ThrowsException(() => - { - options.UseEventLog(null); - }); + Assert.ThrowsException(() => { options.UseEventLog(null!); }); } [TestMethod] public void TestUseCustomDbContextByNullServices() { - var options = new DispatcherOptions(null); + var options = new DispatcherOptions(null!); Assert.IsNull(options.Services); Assert.ThrowsException(() => options.UseEventLog()); } @@ -124,10 +61,13 @@ public void TestGenericEventLog() public async Task TestCustomDbContextAsync() { var options = new DispatcherOptions(new ServiceCollection()); - options.Services.AddMasaDbContext(options => options.UseSqlite(_connection)); + options.Services.AddMasaDbContext(options => + options.UseSqlite(_connection).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); var integrationEventBus = new Mock(); - integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()).Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); + integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => + AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); options.Services.AddScoped(serviceProvider => integrationEventBus.Object); options.Services.AddScoped(); @@ -140,17 +80,18 @@ public async Task TestCustomDbContextAsync() var @event = new OrderPaymentSucceededIntegrationEvent() { OrderId = "1234567890123", - PaymentTime = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds }; var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - using (var transaction = dbContext.Database.BeginTransaction()) - { - await eventLogService.SaveEventAsync(@event, Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); - - await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); - } + await dbContext.Database.EnsureCreatedAsync(); + // using (var transaction = await dbContext.Database.BeginTransactionAsync()) + // { + // await eventLogService.SaveEventAsync(@event, + // Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); + // + // await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); + // } } [TestMethod] @@ -160,7 +101,9 @@ public async Task TestAddMultEventLog() options.Services.AddMasaDbContext(options => options.UseSqlite(_connection)); var integrationEventBus = new Mock(); - integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()).Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); + integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => + AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); options.Services.AddScoped(serviceProvider => integrationEventBus.Object); options.Services.AddScoped(); @@ -173,17 +116,18 @@ public async Task TestAddMultEventLog() var @event = new OrderPaymentSucceededIntegrationEvent() { OrderId = "1234567890123", - PaymentTime = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds }; var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - using (var transaction = dbContext.Database.BeginTransaction()) - { - await eventLogService.SaveEventAsync(@event, Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); - - await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); - } + await dbContext.Database.EnsureCreatedAsync(); + // using (var transaction = dbContext.Database.BeginTransaction()) + // { + // await eventLogService.SaveEventAsync(@event, + // Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); + // + // await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); + // } } [TestMethod] diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj index 1eaa4b8dc..dd5292367 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj @@ -9,12 +9,16 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs index 012f5741b..57215634b 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs @@ -1,7 +1,3 @@ -using MASA.BuildingBlocks.Dispatcher.Events; -using Microsoft.Data.Sqlite; -using Moq; - namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; public class TestBase diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs index b268ee00a..df7c1433c 100644 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs +++ b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs @@ -1,11 +1,15 @@ -global using MASA.BuildingBlocks.Data.Uow; +global using MASA.BuildingBlocks.Data.UoW; +global using MASA.BuildingBlocks.Dispatcher.Events; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Domain.Entities; global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; +global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Infrastructure; global using MASA.Utils.Data.EntityFrameworkCore; +global using Microsoft.Data.Sqlite; global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; global using System.Data.Common; global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs new file mode 100644 index 000000000..f4c4a22c3 --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Commands; + +public record CreateProductionCommand : Command +{ + public string Name { get; set; } + + public int Count { get; set; } +} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs new file mode 100644 index 000000000..aa936c80e --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs @@ -0,0 +1,51 @@ +namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests; + +[TestClass] +public class CQRSTest +{ + private IServiceCollection _services; + private IServiceProvider _serviceProvider; + private IEventBus _eventBus; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + _services.AddEventBus(); + _serviceProvider = _services.BuildServiceProvider(); + _eventBus = _serviceProvider.GetRequiredService(); + } + + + [DataTestMethod] + [DataRow("")] + [DataRow("tom")] + public void TestCommand(string name) + { + var command = new CreateProductionCommand() + { + Name = name, + Count = 0 + }; + _eventBus.PublishAsync(command); + if (string.IsNullOrEmpty(name)) + { + Assert.IsTrue(command.Count == 2); + } + else + { + Assert.IsTrue(command.Count == 1); + } + } + + [TestMethod] + public void TestQuery() + { + var query = new ProductionItemQuery() + { + ProductionId = "1" + }; + _eventBus.PublishAsync(query); + Assert.IsTrue(query.Result == "Apple"); + } +} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs new file mode 100644 index 000000000..b87ada809 --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs @@ -0,0 +1,24 @@ +namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests; + +public class CreateProductionCommandHandler : CommandHandler +{ + [EventHandler(1, Dispatcher.Events.Enums.FailureLevels.ThrowAndCancel, false)] + public override Task HandleAsync(CreateProductionCommand @event) + { + @event.Count++; + if (string.IsNullOrEmpty(@event.Name)) + throw new ArgumentNullException(nameof(@event)); + + if (@event.Id == default(Guid) || @event.CreationTime > DateTime.UtcNow) + throw new ArgumentNullException(nameof(@event)); + + return Task.CompletedTask; + } + + [EventHandler(1)] + public override Task CancelAsync(CreateProductionCommand @event) + { + @event.Count++; + return base.CancelAsync(@event); + } +} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj new file mode 100644 index 000000000..16dccc410 --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj @@ -0,0 +1,27 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs new file mode 100644 index 000000000..43deb0445 --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs @@ -0,0 +1,18 @@ +namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests; + +public class ProductionQueryHandler : QueryHandler +{ + public override Task HandleAsync(ProductionItemQuery @event) + { + if (string.IsNullOrEmpty(@event.ProductionId)) + throw new ArgumentNullException(nameof(@event)); + + if (@event.Id == default(Guid) || @event.CreationTime > DateTime.UtcNow) + throw new ArgumentNullException(nameof(@event)); + + if (@event.ProductionId == "1") + @event.Result = "Apple"; + + return Task.CompletedTask; + } +} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs new file mode 100644 index 000000000..2560231b3 --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs @@ -0,0 +1,8 @@ +namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Queries; + +public record ProductionItemQuery : Query +{ + public override string Result { get; set; } + + public string ProductionId { get; set; } +} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs new file mode 100644 index 000000000..e01b69ac4 --- /dev/null +++ b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs @@ -0,0 +1,8 @@ +global using MASA.BuildingBlocks.Dispatcher.Events; +global using MASA.Contrib.Dispatcher.Events; +global using MASA.Contrib.ReadWriteSpliting.CQRS.Commands; +global using MASA.Contrib.ReadWriteSpliting.CQRS.Queries; +global using MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Commands; +global using MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Queries; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj b/test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj new file mode 100644 index 000000000..6d6fbd4ec --- /dev/null +++ b/test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs b/test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs new file mode 100644 index 000000000..dacff249c --- /dev/null +++ b/test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs @@ -0,0 +1,48 @@ +namespace MASA.Contrib.Service.MinimalAPIs.Tests; + +[TestClass] +public class MinimalAPITest +{ + private WebApplicationBuilder _builder; + + [TestInitialize] + public void Initialize() + { + _builder = WebApplication.CreateBuilder(); + } + + [TestMethod] + public void TestAddMultiServices() + { + _builder.Services.AddServices(_builder); + _builder.Services.AddServices(_builder); + var servicePrvider = _builder.Services.BuildServiceProvider(); + Assert.IsTrue(servicePrvider.GetServices>().Count() == 1); + } + + [TestMethod] + public void AddService() + { + var app = _builder.AddServices(); + Assert.IsTrue(_builder.Services.Any(service => service.ServiceType == typeof(CustomService) && service.Lifetime == ServiceLifetime.Scoped)); + + var servicePrvider = _builder.Services.BuildServiceProvider(); + var customService = servicePrvider.GetService(); + Assert.IsNotNull(customService); + + Assert.ReferenceEquals(customService.App, app); + + Assert.ReferenceEquals(customService.Services, _builder.Services); + + Assert.IsNotNull(customService.GetRequiredService()); + Assert.IsNotNull(customService.GetService()); + + Assert.IsTrue(customService.Test() == 1); + + var newCustomService = servicePrvider.CreateScope().ServiceProvider.GetService(); + Assert.IsNotNull(newCustomService); + + Assert.IsTrue(newCustomService.Test() == 1); + + } +} diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs b/test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs new file mode 100644 index 000000000..b0ce6d588 --- /dev/null +++ b/test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs @@ -0,0 +1,13 @@ +namespace MASA.Contrib.Service.MinimalAPIs.Tests.Services; + +public class CustomService : ServiceBase +{ + private int _times = 0; + + public CustomService(IServiceCollection services) : base(services) + { + _times++; + } + + public int Test() => _times; +} diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs b/test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs new file mode 100644 index 000000000..7315683bd --- /dev/null +++ b/test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs @@ -0,0 +1,4 @@ +global using MASA.Contrib.Service.MinimalAPIs.Tests.Services; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/MASA.Contribs.DDD.Domain.Entities/MASA.Contribs.DDD.Domain.Entities.csproj b/test/MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj similarity index 100% rename from test/MASA.Contribs.DDD.Domain.Entities/MASA.Contribs.DDD.Domain.Entities.csproj rename to test/MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj diff --git a/test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs b/test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs new file mode 100644 index 000000000..342e042f8 --- /dev/null +++ b/test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs @@ -0,0 +1,7 @@ +namespace MASA.Contribs.DDD.Domain.Entities.Tests; + +public class Users : AggregateRoot +{ + public string Name { get; set; } +} + diff --git a/test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs b/test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs new file mode 100644 index 000000000..d6a2a9e7d --- /dev/null +++ b/test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs @@ -0,0 +1 @@ +global using MASA.BuildingBlocks.DDD.Domain.Entities; diff --git a/test/MASA.Contribs.DDD.Domain.Entities/User.cs b/test/MASA.Contribs.DDD.Domain.Entities/User.cs deleted file mode 100644 index 780cc5262..000000000 --- a/test/MASA.Contribs.DDD.Domain.Entities/User.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MASA.Contribs.DDD.Domain.Entities; - -public class User : AggregateRoot -{ - public string Name { get; set; } -} - diff --git a/test/MASA.Contribs.DDD.Domain.Repository/_Imports.cs b/test/MASA.Contribs.DDD.Domain.Repository/_Imports.cs deleted file mode 100644 index e69de29bb..000000000 From 0f19b629eca7f9b31caea1cedf027a7221a5188f Mon Sep 17 00:00:00 2001 From: PollosD <55781685+PollosD@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:19:35 +0800 Subject: [PATCH 02/10] Update package_push_nuget.org.yml --- .github/workflows/package_push_nuget.org.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package_push_nuget.org.yml b/.github/workflows/package_push_nuget.org.yml index 49cc9a214..e702272d3 100644 --- a/.github/workflows/package_push_nuget.org.yml +++ b/.github/workflows/package_push_nuget.org.yml @@ -19,8 +19,9 @@ jobs: with: dotnet-version: '6.0.x' include-prerelease: true + - name: dependencies - - run: git clone -b main https://github.com/masastack/MASA.BuildingBlocks.git ./src/BuildingBlocks + run: git clone -b main https://github.com/masastack/MASA.BuildingBlocks.git ./src/BuildingBlocks - name: restore run: dotnet restore From 356460cd9a61b982aac0b63d6d5483077a0e497b Mon Sep 17 00:00:00 2001 From: PollosD <55781685+PollosD@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:16:54 +0800 Subject: [PATCH 03/10] Update package_push_nuget.org.yml --- .github/workflows/package_push_nuget.org.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package_push_nuget.org.yml b/.github/workflows/package_push_nuget.org.yml index e702272d3..998e8d780 100644 --- a/.github/workflows/package_push_nuget.org.yml +++ b/.github/workflows/package_push_nuget.org.yml @@ -21,7 +21,7 @@ jobs: include-prerelease: true - name: dependencies - run: git clone -b main https://github.com/masastack/MASA.BuildingBlocks.git ./src/BuildingBlocks + run: git clone -b main https://github.com/masastack/MASA.BuildingBlocks.git ./src/BuildingBlocks/MASA.BuildingBlocks - name: restore run: dotnet restore From 78859f4d134a54f83f0f5dcc8787d34bd1f8a197 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 24 Feb 2022 18:37:25 +0800 Subject: [PATCH 04/10] chore: delete submodule --- .gitmodules | 3 --- src/BuildingBlocks/MASA.BuildingBlocks | 1 - 2 files changed, 4 deletions(-) delete mode 160000 src/BuildingBlocks/MASA.BuildingBlocks diff --git a/.gitmodules b/.gitmodules index 5a6352e61..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "src/BuildingBlocks/MASA.BuildingBlocks"] - path = src/BuildingBlocks/MASA.BuildingBlocks - url = https://github.com/masastack/MASA.BuildingBlocks.git diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks deleted file mode 160000 index af4058aed..000000000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit af4058aede124ed29f82d57b903527b6e2ee4ab5 From f34dd5c047802d89f7c0bfcf30954f01bb509686 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 24 Feb 2022 18:39:49 +0800 Subject: [PATCH 05/10] chore: add submodule --- .gitmodules | 3 +++ src/BuildingBlocks/MASA.BuildingBlocks | 1 + 2 files changed, 4 insertions(+) create mode 160000 src/BuildingBlocks/MASA.BuildingBlocks diff --git a/.gitmodules b/.gitmodules index e69de29bb..5a6352e61 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/BuildingBlocks/MASA.BuildingBlocks"] + path = src/BuildingBlocks/MASA.BuildingBlocks + url = https://github.com/masastack/MASA.BuildingBlocks.git diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks new file mode 160000 index 000000000..0b0c99547 --- /dev/null +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -0,0 +1 @@ +Subproject commit 0b0c99547e4a5784813e6e6eb367ffbba518cd65 From 3cf8bcea75eae80d50991bd370e553f6405136cd Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 24 Feb 2022 18:40:24 +0800 Subject: [PATCH 06/10] refactor: change MASA.Contrib to Masa.Contrib --- Directory.Build.props | 24 - LICENSE.txt | 21 - MASA.Contrib.sln | 600 ------------- NuGet.Config | 6 - README.md | 144 ---- README.zh-CN.md | 144 ---- packageIcon.png | Bin 9523 -> 0 bytes .../ConfigurationApiClient.cs | 158 ---- .../ConfigurationApiManage.cs | 28 - .../Internal/ConfigFormats.cs | 8 - .../Internal/ConfigurationAPIBase.cs | 37 - .../Internal/Constants.cs | 12 - .../Internal/DccConfigurationRepository.cs | 92 -- .../Internal/DccFactory.cs | 20 - .../Internal/Model/Property.cs | 8 - .../Internal/Model/PublishRelease.cs | 8 - .../Parser/JsonConfigurationParser.cs | 101 --- .../Parser/PropertyConfigurationParser.cs | 7 - .../MASA.Contrib.BasicAbility.Dcc.csproj | 23 - .../MasaConfigurationExtensions.cs | 204 ----- .../Options/DccConfigurationOptions.cs | 13 - .../Options/DccExpandSectionOptions.cs | 9 - .../Options/DccSectionOptions.cs | 24 - .../MASA.Contrib.BasicAbility.Dcc/README.md | 164 ---- .../README.zh-CN.md | 155 ---- .../MASA.Contrib.BasicAbility.Dcc/_Imports.cs | 24 - src/BuildingBlocks/MASA.BuildingBlocks | 1 - .../LocalMasaConfigurationRepository.cs | 71 -- .../MASA.Contrib.Configuration.csproj | 17 - .../MasaConfigurationBuilder.cs | 52 -- .../MasaConfigurationExtensions.cs | 35 - .../MasaConfigurationOptions.cs | 19 - .../MasaConfigurationProvider.cs | 64 -- .../MasaConfigurationSource.cs | 17 - .../MasaRelationOptions.cs | 33 - .../MASA.Contrib.Configuration/README.md | 135 --- .../README.zh-CN.md | 140 --- .../ServiceCollectionExtensions.cs | 138 --- .../MASA.Contrib.Configuration/_Imports.cs | 15 - .../DispatcherOptionsExtensions.cs | 34 - .../Internal/LinqExtensions.cs | 93 -- .../ServiceCollectionRepositoryExtensions.cs | 105 --- ...SA.Contrib.DDD.Domain.Repository.EF.csproj | 18 - .../README.md | 71 -- .../README.zh-CN.md | 71 -- .../Repository.cs | 213 ----- .../_Imports.cs | 15 - .../MASA.Contrib.DDD.Domain/DomainEventBus.cs | 84 -- .../MASA.Contrib.DDD.Domain/DomainService.cs | 8 - .../Events/DomainCommand.cs | 15 - .../Events/DomainEvent.cs | 16 - .../Events/DomainQuery.cs | 22 - .../Events/IntegrationDomainEvent.cs | 9 - .../Internal/InvokeBuilder.cs | 21 - .../MASA.Contrib.DDD.Domain.csproj | 18 - .../Options/DispatcherOptions.cs | 40 - src/DDD/MASA.Contrib.DDD.Domain/README.md | 119 --- .../MASA.Contrib.DDD.Domain/README.zh-CN.md | 119 --- .../ServiceCollectionExtensions.cs | 67 -- src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs | 15 - .../MASA.Contrib.Data.Contracts.EF.csproj | 19 - .../MASA.Contrib.Data.Contracts.EF/README.md | 31 - .../README.zh-CN.md | 31 - .../ServiceCollectionExtensions.cs | 23 - .../SoftDelete/QueryFilterProvider.cs | 19 - .../SoftDelete/SoftDeleteSaveChangesFilter.cs | 17 - .../_Imports.cs | 9 - .../DispatcherOptionsExtensions.cs | 43 - .../MASA.Contrib.Data.UoW.EF.csproj | 20 - src/Data/MASA.Contrib.Data.UoW.EF/README.md | 20 - .../MASA.Contrib.Data.UoW.EF/README.zh-CN.md | 20 - .../MASA.Contrib.Data.UoW.EF/Transaction.cs | 9 - .../MASA.Contrib.Data.UoW.EF/UnitOfWork.cs | 66 -- src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs | 11 - .../DispatcherOptionsExtensions.cs | 26 - .../Enums/FailureLevels.cs | 10 - .../MASA.Contrib.Dispatcher.Events/Event.cs | 12 - .../EventBus.cs | 67 -- .../EventHandlerAttribute.cs | 150 ---- .../Dispatch/DispatchRelationNetwork.cs | 99 --- .../Internal/Dispatch/Dispatcher.cs | 68 -- .../Internal/Dispatch/DispatcherBase.cs | 128 --- .../Internal/Dispatch/SagaDispatcher.cs | 118 --- .../Internal/DispatcherExtensions.cs | 24 - .../Internal/Expressions/InvokeBuilder.cs | 56 -- .../Middleware/TransactionMiddleware.cs | 49 -- .../MASA.Contrib.Dispatcher.Events.csproj | 22 - .../Options/DispatchRelationOptions.cs | 21 - .../Options/DispatcherOptions.cs | 36 - .../MASA.Contrib.Dispatcher.Events/README.md | 194 ----- .../README.zh-CN.md | 194 ----- .../ServiceCollectionExtensions.cs | 61 -- .../Strategies/ExecutionStrategy.cs | 45 - .../Strategies/IExecutionStrategy.cs | 7 - .../Strategies/StrategyOptions.cs | 19 - .../_Imports.cs | 18 - .../DispatcherOptionsExtensions.cs | 22 - .../IProcessingServer.cs | 6 - .../IProcessor.cs | 13 - .../IntegrationEvent.cs | 18 - .../IntegrationEventBus.cs | 100 --- .../IntegrationEventHostedService.cs | 20 - .../Internal/IntegrationEventLogItem.cs | 31 - .../Internal/LocalQueueProcessor.cs | 60 -- ...b.Dispatcher.IntegrationEvents.Dapr.csproj | 21 - .../Options/DispatcherOptions.cs | 158 ---- .../DeleteLocalQueueExpiresProcessor.cs | 24 - .../DeletePublishedExpireEventProcessor.cs | 32 - .../Processor/InfiniteLoopProcessor.cs | 35 - .../Processor/ProcessorBase.cs | 19 - .../Processor/RetryByDataProcessor.cs | 76 -- .../Processor/RetryByLocalQueueProcessor.cs | 72 -- .../README.md | 104 --- .../README.zh-CN.md | 105 --- .../Servers/DefaultHostedService.cs | 20 - .../ServiceCollectionExtensions.cs | 53 -- .../_Imports.cs | 19 - .../DispatcherOptionsExtensions.cs | 56 -- .../IntegrationEventLogContext.cs | 53 -- .../IntegrationEventLogService.cs | 158 ---- .../Internal/DbContextExtensions.cs | 27 - .../Internal/QueryFilterProvider.cs | 6 - .../Internal/SaveChangesFilter.cs | 6 - ...cher.IntegrationEvents.EventLogs.EF.csproj | 20 - .../README.md | 24 - .../README.zh-CN.md | 24 - .../_Imports.cs | 18 - .../Commands/Command.cs | 15 - .../Commands/CommandHandler.cs | 12 - ...MASA.Contrib.ReadWriteSpliting.CQRS.csproj | 19 - .../Queries/Query.cs | 15 - .../Queries/QueryHandler.cs | 8 - .../README.md | 88 -- .../README.zh-CN.md | 88 -- .../_Imports.cs | 6 - .../MASA.Contrib.Service.MinimalAPIs.csproj | 18 - .../README.md | 53 -- .../README.zh-CN.md | 53 -- .../ServiceBase.cs | 119 --- .../ServiceCollectionExtensions.cs | 42 - .../_Imports.cs | 8 - .../DccClientTest.cs | 388 --------- .../DccManageTest.cs | 162 ---- .../DccTest.cs | 799 ------------------ .../Internal/Common/SerializeCommon.cs | 7 - .../Internal/Config/Property.cs | 8 - .../Internal/Config/PublishRelease.cs | 8 - .../Internal/CustomTrigger.cs | 26 - .../Internal/Enum/ConfigFormats.cs | 8 - .../Internal/Model/Brands.cs | 14 - ...MASA.Contrib.BasicAbility.Dcc.Tests.csproj | 36 - .../_Imports.cs | 14 - .../appsettings.json | 23 - .../expandSections.json | 32 - .../ErrorKafkaOptions.cs | 12 - .../KafkaOptions.cs | 15 - ...iguration.ErrorSectionAutoMap.Tests.csproj | 14 - .../_Imports.cs | 2 - ...tion.MountErrorSectionAutoMap.Tests.csproj | 14 - .../MountSectionRedisOptions.cs | 12 - .../_Imports.cs | 2 - .../Config/RabbitMqOptions.cs | 16 - .../Config/RedisOptions.cs | 10 - .../Config/SystemOptions.cs | 14 - .../ConfigurationTest.cs | 266 ------ .../MASA.Contrib.Configuration.Tests.csproj | 43 - .../_Imports.cs | 10 - .../appsettings.json | 9 - .../rabbitMq.json | 7 - .../redis.json | 5 - ...in.Repository.EF.CombinedKeys.Tests.csproj | 14 - .../Students.cs | 28 - .../_Imports.cs | 1 - .../Courses.cs | 19 - ...ository.EF.CombinedKeysNoFind.Tests.csproj | 14 - .../_Imports.cs | 1 - .../Entities/User.cs | 8 - ...epository.EF.CustomRepository.Tests.csproj | 14 - .../Repositories/IUserRepository.cs | 8 - .../_Imports.cs | 2 - .../Hobbies.cs | 16 - ...D.Domain.Repository.EF.Entity.Tests.csproj | 14 - .../_Imports.cs | 1 - .../BaseRepositoryTest.cs | 78 -- .../Domain/Entities/Address.cs | 23 - .../Domain/Entities/OrderItem.cs | 16 - .../Domain/Entities/Orders.cs | 40 - .../Domain/Repositories/IOrderRepository.cs | 6 - .../Infrastructure/CustomDbContext.cs | 23 - .../Repositories/OrderRepository.cs | 23 - ...trib.DDD.Domain.Repository.EF.Tests.csproj | 31 - .../RepositoryTest.cs | 273 ------ .../TestBase.cs | 17 - .../_Imports.cs | 23 - .../DomainEventBusTest.cs | 349 -------- .../DomainIntegrationEventBusTest.cs | 49 -- .../Events/CreateProductDomainCommand.cs | 6 - .../Events/ForgetPasswordEvent.cs | 10 - .../PaymentFailedIntegrationDomainEvent.cs | 8 - .../Events/PaymentSucceededDomainEvent.cs | 6 - .../PaymentSucceededIntegraionDomainEvent.cs | 6 - .../Events/ProductItemDomainQuery.cs | 8 - ...sterUserSucceededDomainIntegrationEvent.cs | 8 - .../PaymentSucceededDomainEventHandller.cs | 19 - .../MASA.Contrib.DDD.Domain.Tests.csproj | 29 - .../Services/UserDomainService.cs | 16 - .../MASA.Contrib.DDD.Domain.Tests/_Imports.cs | 16 - .../Domain/Entities/Courses.cs | 14 - .../Domain/Entities/Students.cs | 16 - .../Infrastructure/CustomDbContext.cs | 12 - ...ASA.Contrib.Data.Contracts.EF.Tests.csproj | 28 - .../SoftDeleteTest.cs | 117 --- .../_Imports.cs | 10 - .../CustomerDbContext.cs | 44 - .../MASA.Contrib.Data.UoW.EF.Tests.csproj | 31 - .../TestBase.cs | 17 - .../TestUnitOfWork.cs | 171 ---- .../_Imports.cs | 11 - .../Benchmarks.cs | 61 -- .../Extensions/EventHandlers/CouponHandler.cs | 50 -- .../Extensions/EventHandlers/NoticeHandler.cs | 63 -- .../Extensions/Events/ForgetPasswordEvent.cs | 8 - .../Extensions/Events/RegisterUserEvent.cs | 8 - .../Middleware/LoggingMiddleware.cs | 15 - ...atcher.Events.BenchmarkDotnet.Tests.csproj | 22 - .../Program.cs | 14 - .../_Imports.cs | 15 - .../EventHandlers/AddGoodsHandler.cs | 10 - .../Events/AddGoodsEvent.cs | 10 - ....Events.CheckMethodsParameter.Tests.csproj | 14 - .../_Imports.cs | 2 - .../EventHandlers/DeleteGoodsHandler.cs | 10 - .../Events/DeleteGoodsEvent.cs | 6 - ....CheckMethodsParameterNotNull.Tests.csproj | 14 - .../_Imports.cs | 0 .../EventHandlers/AddCatalogHandler.cs | 16 - .../Events/AddCatalogEvent.cs | 8 - ...nts.CheckMethodsParameterType.Tests.csproj | 14 - .../_Imports.cs | 1 - .../EventHandlers/AddBasketHandler.cs | 14 - .../Events/AddBasketEvent.cs | 8 - ...tcher.Events.CheckMethodsType.Tests.csproj | 14 - .../_Imports.cs | 2 - .../EventHandlers/UserEventHandler.cs | 10 - .../Events/BindPhoneNumberEvent.cs | 8 - ...cher.Events.OnlyCancelHandler.Tests.csproj | 14 - .../_Imports.cs | 1 - .../EventHandlers/EditCategoryHandler.cs | 21 - .../Events/EditCategoryEvent.cs | 8 - ...tcher.Events.OrderEqualBySaga.Tests.csproj | 14 - .../_Imports.cs | 3 - .../OrderStockConfirmedHandler.cs | 14 - .../Events/OrderStockConfirmedEvent.cs | 6 - ...ts.OrderLessThanZeroByFeature.Tests.csproj | 14 - .../_Imports.cs | 2 - .../EventHandlers/EditGoodsHandler.cs | 14 - .../Events/EditGoodsEvent.cs | 10 - ...vents.OrderLessThanZeroBySaga.Tests.csproj | 14 - .../_Imports.cs | 3 - .../AssemblyResolutionTests.cs | 122 --- .../ChoreTest.cs | 102 --- .../ChangePasswordEventHandler.cs | 39 - .../EventHandlers/MarketingEventHandler.cs | 24 - .../OrderPaymentSucceededEventHandler.cs | 23 - .../EventHandlers/ShipOrderEventHandler.cs | 55 -- .../EventHandlers/ShoppingCardEventHandler.cs | 14 - .../EventHandlers/TransferEventHandler.cs | 80 -- .../EventHandlers/UserEventHandler.cs | 27 - .../Events/AddShoppingCartEvent.cs | 8 - .../Events/AddUserEvent.cs | 15 - .../Events/ChangePasswordEvent.cs | 12 - .../Events/ComputeEvent.cs | 26 - .../Events/DeductionMoneyEvent.cs | 12 - .../Events/EditUserEvent.cs | 8 - .../Events/ForgotPasswordEvent.cs | 8 - .../Events/IncreaseMoneyEvent.cs | 12 - .../OrderPaymentFailedIntegrationEvent.cs | 22 - .../Events/OrderPaymentSucceededEvent.cs | 8 - .../Events/ShipOrderEvent.cs | 10 - .../Events/TransferEvent.cs | 10 - .../FeaturesTest.cs | 278 ------ ...ASA.Contrib.Dispatcher.Events.Tests.csproj | 41 - .../Middleware/LoggingMiddleware.cs | 14 - .../SagaTest.cs | 131 --- .../TestBase.cs | 44 - .../_Imports.cs | 20 - .../Events/CreateUserEvent.cs | 21 - .../Events/RegisterUserIntegrationEvent.cs | 20 - .../IntegrationEventBusTest.cs | 370 -------- ...atcher.IntegrationEvents.Dapr.Tests.csproj | 27 - .../_Imports.cs | 16 - .../Domain/Entities/User.cs | 8 - .../Events/IntegrationEvent.cs | 21 - .../OrderPaymentSucceededIntegrationEvent.cs | 10 - .../Infrastructure/CustomDbContext.cs | 11 - .../IntegrationEventLogContextTest.cs | 23 - .../IntegrationEventLogServiceTest.cs | 144 ---- ...ntegrationEvents.EventLogs.EF.Tests.csproj | 28 - .../TestBase.cs | 41 - .../_Imports.cs | 15 - .../Commands/CreateProductionCommand.cs | 8 - .../CqrsTest.cs | 51 -- .../CreateProductionCommandHandler.cs | 24 - ...ontrib.ReadWriteSpliting.CQRS.Tests.csproj | 27 - .../ProductionQueryHandler.cs | 18 - .../Queries/ProductionItemQuery.cs | 8 - .../_Imports.cs | 8 - ...A.Contrib.Service.MinimalAPIs.Tests.csproj | 26 - .../MinimalAPITest.cs | 48 -- .../Services/CustomService.cs | 13 - .../_Imports.cs | 4 - ....Contribs.DDD.Domain.Entities.Tests.csproj | 14 - .../Users.cs | 7 - .../_Imports.cs | 1 - 314 files changed, 14052 deletions(-) delete mode 100644 Directory.Build.props delete mode 100644 LICENSE.txt delete mode 100644 MASA.Contrib.sln delete mode 100644 NuGet.Config delete mode 100644 README.md delete mode 100644 README.zh-CN.md delete mode 100644 packageIcon.png delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiClient.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md delete mode 100644 src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs delete mode 160000 src/BuildingBlocks/MASA.BuildingBlocks delete mode 100644 src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/README.md delete mode 100644 src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md delete mode 100644 src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs delete mode 100644 src/Configuration/MASA.Contrib.Configuration/_Imports.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/DomainService.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/README.md delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs delete mode 100644 src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/README.md delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs delete mode 100644 src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/README.md delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs delete mode 100644 src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Enums/FailureLevels.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventHandlerAttribute.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md delete mode 100644 src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/CommandHandler.cs delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md delete mode 100644 src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs delete mode 100644 src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj delete mode 100644 src/Service/MASA.Contrib.Service.MinimalAPIs/README.md delete mode 100644 src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md delete mode 100644 src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs delete mode 100644 src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs delete mode 100644 src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json delete mode 100644 test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json delete mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs delete mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs delete mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj delete mode 100644 test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj delete mode 100644 test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs delete mode 100644 test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs delete mode 100644 test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs delete mode 100644 test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs delete mode 100644 test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs delete mode 100644 test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj delete mode 100644 test/MASA.Contrib.Configuration.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Configuration.Tests/appsettings.json delete mode 100644 test/MASA.Contrib.Configuration.Tests/rabbitMq.json delete mode 100644 test/MASA.Contrib.Configuration.Tests/redis.json delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/CreateProductDomainCommand.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/ProductItemDomainQuery.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs delete mode 100644 test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs delete mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs delete mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs delete mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj delete mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs delete mode 100644 test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs delete mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj delete mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs delete mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs delete mode 100644 test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs delete mode 100644 test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs delete mode 100644 test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs delete mode 100644 test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs delete mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj delete mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs delete mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs delete mode 100644 test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs delete mode 100644 test/MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj delete mode 100644 test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs delete mode 100644 test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index a0865029c..000000000 --- a/Directory.Build.props +++ /dev/null @@ -1,24 +0,0 @@ - - - $(AssemblyName) - packageIcon.png - masastack - ยฉ masastack Corporation. All rights reserved. - packageIcon.png - https://github.com/masastack/MASA.Contrib - git - true - $(MSBuildThisFileDirectory) - LICENSE.txt - - - - True - - - - True - - - - diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 3f96be793..000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) MASA Stack - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/MASA.Contrib.sln b/MASA.Contrib.sln deleted file mode 100644 index 02a1db545..000000000 --- a/MASA.Contrib.sln +++ /dev/null @@ -1,600 +0,0 @@ -๏ปฟ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31521.260 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BasicAbility", "BasicAbility", "{5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{59DA3D5F-9E39-4173-8C31-126967CC189F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dispatcher", "Dispatcher", "{FBD326D3-E59C-433E-A88E-14E179E3093D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "I18n", "I18n", "{EA2668AF-28E3-42C5-9FA5-8C9FF377180E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Isolation", "Isolation", "{022D6FF5-4B65-4213-9A97-C69E2B2F99E1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Observability", "Observability", "{75050CBC-A0F2-408A-A582-54EF37450B29}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadWriteSpliting", "ReadWriteSpliting", "{509BDB5A-5D32-478F-BF27-F0470C18C7C9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SearchEngine", "SearchEngine", "{8C39C640-0E8A-43A7-890C-9742B6B70AA4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Service", "Service", "{593A3114-D1E0-47ED-BC37-58E08886175B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CQRS", "CQRS", "{DA885E64-C5E2-4C22-8C2A-26E68A593F29}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{9F6F9899-D5F1-444A-BE56-64F949550D22}" - ProjectSection(SolutionItems) = preProject - nuget.config = nuget.config - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{38E6C400-90C0-493E-9266-C1602E229F1B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.Dispatcher.Events", "MASA.Contrib.Dispatcher.Events", "{2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Service.MinimalAPIs", "src\Service\MASA.Contrib.Service.MinimalAPIs\MASA.Contrib.Service.MinimalAPIs.csproj", "{ED301FA5-4E70-460B-A0D4-1D79D135769F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DDD", "DDD", "{21180442-A6A5-4239-A2AD-33FF5BB80E72}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data", "Data", "{E33ADF54-4D35-49B7-BDA6-412587CA39FF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events", "src\Dispatcher\MASA.Contrib.Dispatcher.Events\MASA.Contrib.Dispatcher.Events.csproj", "{1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests", "test\MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests\MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj", "{D55A7D3B-9C24-4029-BC03-41B28AD11DB6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests", "test\MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests\MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj", "{89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests", "test\MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests\MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj", "{2E172027-1B85-474E-A238-21B2DBDB895F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests", "test\MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests\MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj", "{62760E2C-D3D6-4824-997F-35033E6EB92C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests", "test\MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests\MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj", "{965C85E2-D94E-43DE-BFC2-B9D157242EBB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests", "test\MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests\MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj", "{7D083C64-FF32-43C4-A82C-32C4A4EC1414}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests", "test\MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests\MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj", "{DE2E92D3-929F-40E7-B8D0-502A57170A3E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests", "test\MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests\MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj", "{0B139C21-8AFD-41CD-82FE-36E64FDEDE50}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.Tests", "test\MASA.Contrib.Dispatcher.Events.Tests\MASA.Contrib.Dispatcher.Events.Tests.csproj", "{3870E8F1-B269-425D-8B03-58835FD53610}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF", "src\Dispatcher\MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF\MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj", "{519A99D2-8094-48EC-A888-C0B4E017A4C1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.IntegrationEvents.Dapr", "src\Dispatcher\MASA.Contrib.Dispatcher.IntegrationEvents.Dapr\MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj", "{E946A129-34ED-4069-B44E-EC7B98751006}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests", "test\MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests\MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj", "{F3DA1941-3610-48F3-901A-E2E873979BFD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.ReadWriteSpliting.CQRS", "src\ReadWriteSpliting\CQRS\MASA.Contrib.ReadWriteSpliting.CQRS\MASA.Contrib.ReadWriteSpliting.CQRS.csproj", "{EA23C277-97E4-4FBC-A53B-37048813E14F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain", "src\DDD\MASA.Contrib.DDD.Domain\MASA.Contrib.DDD.Domain.csproj", "{0FF64D9E-98D7-46B1-90FB-C0364C76D65A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF", "src\DDD\MASA.Contrib.DDD.Domain.Repository.EF\MASA.Contrib.DDD.Domain.Repository.EF.csproj", "{D375233D-8AAC-4234-BC0D-3D103C600C19}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.Contracts.EF", "src\Data\MASA.Contrib.Data.Contracts.EF\MASA.Contrib.Data.Contracts.EF.csproj", "{33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Tests", "test\MASA.Contrib.DDD.Domain.Tests\MASA.Contrib.DDD.Domain.Tests.csproj", "{EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.Tests\MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj", "{E893C913-98A0-4BB3-A32B-3871BE3C5C53}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests", "test\MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests\MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj", "{761C3313-A669-465F-A384-9E118FCE4F89}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.DDD.Domain", "MASA.Contrib.DDD.Domain", "{13EDB361-AF88-4F89-B4AB-46622BCCBC37}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.DDD.Domain.Repository.EF", "MASA.Contrib.DDD.Domain.Repository.EF", "{880E8263-AECC-4793-BD28-7CD03650D124}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.UoW.EF", "src\Data\MASA.Contrib.Data.UoW.EF\MASA.Contrib.Data.UoW.EF.csproj", "{1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.UoW.EF.Tests", "test\MASA.Contrib.Data.UoW.EF.Tests\MASA.Contrib.Data.UoW.EF.Tests.csproj", "{1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.BasicAbility.Dcc", "src\BasicAbility\MASA.Contrib.BasicAbility.Dcc\MASA.Contrib.BasicAbility.Dcc.csproj", "{FDF1C618-4C68-485E-A881-4FFF04EDF6E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration", "src\Configuration\MASA.Contrib.Configuration\MASA.Contrib.Configuration.csproj", "{C056C688-8FFC-42BC-B4EA-EF3808A8A12C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.ReadWriteSpliting.CQRS.Tests", "test\MASA.Contrib.ReadWriteSpliting.CQRS.Tests\MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj", "{428CDAF3-957A-4017-82EA-70737F205546}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration.Tests", "test\MASA.Contrib.Configuration.Tests\MASA.Contrib.Configuration.Tests.csproj", "{DB93B639-899D-4B2C-AF8A-47B4BC6B3776}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.BasicAbility.Dcc.Tests", "test\MASA.Contrib.BasicAbility.Dcc.Tests\MASA.Contrib.BasicAbility.Dcc.Tests.csproj", "{85BCA106-4A6F-4BEE-A748-E61A24D12DBD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MASA.Contrib.Configuration", "MASA.Contrib.Configuration", "{9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Service.MinimalAPIs.Tests", "test\MASA.Contrib.Service.MinimalAPIs.Tests\MASA.Contrib.Service.MinimalAPIs.Tests.csproj", "{A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contribs.DDD.Domain.Entities.Tests", "test\MASA.Contribs.DDD.Domain.Entities.Tests\MASA.Contribs.DDD.Domain.Entities.Tests.csproj", "{B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Data.Contracts.EF.Tests", "test\MASA.Contrib.Data.Contracts.EF.Tests\MASA.Contrib.Data.Contracts.EF.Tests.csproj", "{5A163042-B03A-4063-85FF-22D4C5BB5B90}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests", "test\MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests\MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj", "{84EFF9E1-6852-458F-8D57-62E3F084EA0F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests", "test\MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests\MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj", "{427822F2-7A20-4E3A-B45C-43CE855003C1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj", "{99067BDF-2C6A-47F8-913D-3FF9F2A69F98}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests\MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj", "{A4DE46BD-1FA4-494B-80DA-6EB370686F17}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests\MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj", "{7909A736-6C1E-4622-9BE7-37EF0839FA05}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests", "test\MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests\MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj", "{71E02AFA-06A0-4527-923C-6666B3D66542}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests", "test\MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests\MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj", "{1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{DC578D74-98F0-4F19-A230-CFA8DAEE0AF1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Configuration", "src\BuildingBlocks\MASA.BuildingBlocks\src\Configuration\MASA.BuildingBlocks.Configuration\MASA.BuildingBlocks.Configuration.csproj", "{374B7E56-A815-4F56-A4C2-6094B5A97EE7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Data.Contracts", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\MASA.BuildingBlocks.Data.Contracts\MASA.BuildingBlocks.Data.Contracts.csproj", "{7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Data.UoW", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\MASA.BuildingBlocks.Data.UoW\MASA.BuildingBlocks.Data.UoW.csproj", "{E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.DDD.Domain", "src\BuildingBlocks\MASA.BuildingBlocks\src\DDD\MASA.BuildingBlocks.DDD.Domain\MASA.BuildingBlocks.DDD.Domain.csproj", "{2971858E-2CCA-4688-B8CA-84F130AD5AA9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Dispatcher.Events", "src\BuildingBlocks\MASA.BuildingBlocks\src\Dispatcher\MASA.BuildingBlocks.Dispatcher.Events\MASA.BuildingBlocks.Dispatcher.Events.csproj", "{2503F67B-63BB-4364-8B31-1DD3049C92B7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Dispatcher.IntegrationEvents", "src\BuildingBlocks\MASA.BuildingBlocks\src\Dispatcher\MASA.BuildingBlocks.Dispatcher.IntegrationEvents\MASA.BuildingBlocks.Dispatcher.IntegrationEvents.csproj", "{88BC8170-9123-48C0-A914-11D3CE805196}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.ReadWriteSpliting.CQRS", "src\BuildingBlocks\MASA.BuildingBlocks\src\ReadWriteSpliting\MASA.BuildingBlocks.ReadWriteSpliting.CQRS\MASA.BuildingBlocks.ReadWriteSpliting.CQRS.csproj", "{316B1D0A-9CF7-4E5C-A39A-8A389B075A19}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.SearchEngine.AutoComplete", "src\BuildingBlocks\MASA.BuildingBlocks\src\SearchEngine\MASA.BuildingBlocks.SearchEngine.AutoComplete\MASA.BuildingBlocks.SearchEngine.AutoComplete.csproj", "{2F4986D6-3F56-4C05-8A1D-399594F96093}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MASA.BuildingBlocks.Service.MinimalAPIs", "src\BuildingBlocks\MASA.BuildingBlocks\src\Service\MASA.BuildingBlocks.Service.MinimalAPIs\MASA.BuildingBlocks.Service.MinimalAPIs.csproj", "{E72E105D-B15F-4D69-9A13-CAA49D4889D6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|x64.ActiveCfg = Debug|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|x64.Build.0 = Debug|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|Any CPU.Build.0 = Release|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|x64.ActiveCfg = Release|Any CPU - {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|x64.Build.0 = Release|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|x64.ActiveCfg = Debug|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|x64.Build.0 = Debug|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|Any CPU.Build.0 = Release|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|x64.ActiveCfg = Release|Any CPU - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|x64.Build.0 = Release|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|x64.ActiveCfg = Debug|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|x64.Build.0 = Debug|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|Any CPU.Build.0 = Release|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|x64.ActiveCfg = Release|Any CPU - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|x64.Build.0 = Release|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|x64.ActiveCfg = Debug|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|x64.Build.0 = Debug|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|Any CPU.Build.0 = Release|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|x64.ActiveCfg = Release|Any CPU - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|x64.Build.0 = Release|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|x64.ActiveCfg = Debug|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|x64.Build.0 = Debug|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|Any CPU.Build.0 = Release|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|x64.ActiveCfg = Release|Any CPU - {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|x64.Build.0 = Release|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|x64.ActiveCfg = Debug|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|x64.Build.0 = Debug|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|Any CPU.Build.0 = Release|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|x64.ActiveCfg = Release|Any CPU - {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|x64.Build.0 = Release|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|x64.ActiveCfg = Debug|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|x64.Build.0 = Debug|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|Any CPU.Build.0 = Release|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|x64.ActiveCfg = Release|Any CPU - {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|x64.Build.0 = Release|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|x64.ActiveCfg = Debug|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|x64.Build.0 = Debug|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|Any CPU.Build.0 = Release|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|x64.ActiveCfg = Release|Any CPU - {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|x64.Build.0 = Release|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|x64.Build.0 = Debug|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|Any CPU.Build.0 = Release|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|x64.ActiveCfg = Release|Any CPU - {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|x64.Build.0 = Release|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|x64.ActiveCfg = Debug|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|x64.Build.0 = Debug|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|Any CPU.Build.0 = Release|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|x64.ActiveCfg = Release|Any CPU - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|x64.Build.0 = Release|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|x64.ActiveCfg = Debug|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|x64.Build.0 = Debug|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Release|Any CPU.Build.0 = Release|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Release|x64.ActiveCfg = Release|Any CPU - {3870E8F1-B269-425D-8B03-58835FD53610}.Release|x64.Build.0 = Release|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|x64.ActiveCfg = Debug|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|x64.Build.0 = Debug|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|Any CPU.Build.0 = Release|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|x64.ActiveCfg = Release|Any CPU - {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|x64.Build.0 = Release|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|x64.ActiveCfg = Debug|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|x64.Build.0 = Debug|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Release|Any CPU.Build.0 = Release|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Release|x64.ActiveCfg = Release|Any CPU - {E946A129-34ED-4069-B44E-EC7B98751006}.Release|x64.Build.0 = Release|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|x64.ActiveCfg = Debug|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|x64.Build.0 = Debug|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|Any CPU.Build.0 = Release|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|x64.ActiveCfg = Release|Any CPU - {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|x64.Build.0 = Release|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|x64.ActiveCfg = Debug|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|x64.Build.0 = Debug|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|Any CPU.Build.0 = Release|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|x64.ActiveCfg = Release|Any CPU - {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|x64.Build.0 = Release|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|x64.ActiveCfg = Debug|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|x64.Build.0 = Debug|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|Any CPU.Build.0 = Release|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|x64.ActiveCfg = Release|Any CPU - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|x64.Build.0 = Release|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|x64.ActiveCfg = Debug|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|x64.Build.0 = Debug|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|Any CPU.Build.0 = Release|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|x64.ActiveCfg = Release|Any CPU - {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|x64.Build.0 = Release|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|x64.ActiveCfg = Debug|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|x64.Build.0 = Debug|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|Any CPU.Build.0 = Release|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|x64.ActiveCfg = Release|Any CPU - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|x64.Build.0 = Release|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|x64.ActiveCfg = Debug|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|x64.Build.0 = Debug|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|Any CPU.Build.0 = Release|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|x64.ActiveCfg = Release|Any CPU - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|x64.Build.0 = Release|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|x64.ActiveCfg = Debug|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|x64.Build.0 = Debug|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|Any CPU.Build.0 = Release|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|x64.ActiveCfg = Release|Any CPU - {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|x64.Build.0 = Release|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|Any CPU.Build.0 = Debug|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|x64.ActiveCfg = Debug|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|x64.Build.0 = Debug|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Release|Any CPU.ActiveCfg = Release|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Release|Any CPU.Build.0 = Release|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Release|x64.ActiveCfg = Release|Any CPU - {761C3313-A669-465F-A384-9E118FCE4F89}.Release|x64.Build.0 = Release|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|x64.ActiveCfg = Debug|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|x64.Build.0 = Debug|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|Any CPU.Build.0 = Release|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|x64.ActiveCfg = Release|Any CPU - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|x64.Build.0 = Release|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|x64.ActiveCfg = Debug|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|x64.Build.0 = Debug|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|Any CPU.Build.0 = Release|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|x64.ActiveCfg = Release|Any CPU - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|x64.Build.0 = Release|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|x64.ActiveCfg = Debug|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|x64.Build.0 = Debug|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|Any CPU.Build.0 = Release|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|x64.ActiveCfg = Release|Any CPU - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|x64.Build.0 = Release|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|x64.ActiveCfg = Debug|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|x64.Build.0 = Debug|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|Any CPU.Build.0 = Release|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|x64.ActiveCfg = Release|Any CPU - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|x64.Build.0 = Release|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Debug|Any CPU.Build.0 = Debug|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Debug|x64.ActiveCfg = Debug|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Debug|x64.Build.0 = Debug|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Release|Any CPU.ActiveCfg = Release|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Release|Any CPU.Build.0 = Release|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Release|x64.ActiveCfg = Release|Any CPU - {428CDAF3-957A-4017-82EA-70737F205546}.Release|x64.Build.0 = Release|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|x64.Build.0 = Debug|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|Any CPU.Build.0 = Release|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|x64.ActiveCfg = Release|Any CPU - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|x64.Build.0 = Release|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|x64.ActiveCfg = Debug|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|x64.Build.0 = Debug|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|Any CPU.Build.0 = Release|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|x64.ActiveCfg = Release|Any CPU - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|x64.Build.0 = Release|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|x64.ActiveCfg = Debug|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|x64.Build.0 = Debug|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|Any CPU.Build.0 = Release|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|x64.ActiveCfg = Release|Any CPU - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|x64.Build.0 = Release|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|x64.ActiveCfg = Debug|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|x64.Build.0 = Debug|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|Any CPU.Build.0 = Release|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|x64.ActiveCfg = Release|Any CPU - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|x64.Build.0 = Release|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|x64.ActiveCfg = Debug|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|x64.Build.0 = Debug|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|Any CPU.Build.0 = Release|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|x64.ActiveCfg = Release|Any CPU - {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|x64.Build.0 = Release|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|x64.ActiveCfg = Debug|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|x64.Build.0 = Debug|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|Any CPU.Build.0 = Release|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|x64.ActiveCfg = Release|Any CPU - {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|x64.Build.0 = Release|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|x64.ActiveCfg = Debug|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|x64.Build.0 = Debug|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|Any CPU.Build.0 = Release|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|x64.ActiveCfg = Release|Any CPU - {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|x64.Build.0 = Release|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|x64.ActiveCfg = Debug|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|x64.Build.0 = Debug|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|Any CPU.Build.0 = Release|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|x64.ActiveCfg = Release|Any CPU - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|x64.Build.0 = Release|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|x64.ActiveCfg = Debug|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|x64.Build.0 = Debug|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|Any CPU.Build.0 = Release|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|x64.ActiveCfg = Release|Any CPU - {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|x64.Build.0 = Release|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|x64.ActiveCfg = Debug|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|x64.Build.0 = Debug|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|Any CPU.Build.0 = Release|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|x64.ActiveCfg = Release|Any CPU - {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|x64.Build.0 = Release|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|x64.ActiveCfg = Debug|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|x64.Build.0 = Debug|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|Any CPU.Build.0 = Release|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|x64.ActiveCfg = Release|Any CPU - {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|x64.Build.0 = Release|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|x64.ActiveCfg = Debug|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|x64.Build.0 = Debug|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|Any CPU.Build.0 = Release|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|x64.ActiveCfg = Release|Any CPU - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|x64.Build.0 = Release|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|x64.ActiveCfg = Debug|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|x64.Build.0 = Debug|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|Any CPU.Build.0 = Release|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|x64.ActiveCfg = Release|Any CPU - {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|x64.Build.0 = Release|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|x64.ActiveCfg = Debug|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|x64.Build.0 = Debug|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|Any CPU.Build.0 = Release|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|x64.ActiveCfg = Release|Any CPU - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|x64.Build.0 = Release|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|x64.ActiveCfg = Debug|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|x64.Build.0 = Debug|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|Any CPU.Build.0 = Release|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|x64.ActiveCfg = Release|Any CPU - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|x64.Build.0 = Release|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|x64.ActiveCfg = Debug|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|x64.Build.0 = Debug|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|Any CPU.Build.0 = Release|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|x64.ActiveCfg = Release|Any CPU - {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|x64.Build.0 = Release|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|x64.ActiveCfg = Debug|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|x64.Build.0 = Debug|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|Any CPU.Build.0 = Release|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|x64.ActiveCfg = Release|Any CPU - {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|x64.Build.0 = Release|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|x64.ActiveCfg = Debug|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|x64.Build.0 = Debug|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Release|Any CPU.Build.0 = Release|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Release|x64.ActiveCfg = Release|Any CPU - {88BC8170-9123-48C0-A914-11D3CE805196}.Release|x64.Build.0 = Release|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|x64.ActiveCfg = Debug|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|x64.Build.0 = Debug|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|Any CPU.Build.0 = Release|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|x64.ActiveCfg = Release|Any CPU - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|x64.Build.0 = Release|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|x64.ActiveCfg = Debug|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|x64.Build.0 = Debug|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|Any CPU.Build.0 = Release|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|x64.ActiveCfg = Release|Any CPU - {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|x64.Build.0 = Release|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|x64.ActiveCfg = Debug|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|x64.Build.0 = Debug|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|Any CPU.Build.0 = Release|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|x64.ActiveCfg = Release|Any CPU - {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {59DA3D5F-9E39-4173-8C31-126967CC189F} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {FBD326D3-E59C-433E-A88E-14E179E3093D} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {EA2668AF-28E3-42C5-9FA5-8C9FF377180E} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {75050CBC-A0F2-408A-A582-54EF37450B29} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {509BDB5A-5D32-478F-BF27-F0470C18C7C9} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {8C39C640-0E8A-43A7-890C-9742B6B70AA4} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {593A3114-D1E0-47ED-BC37-58E08886175B} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {DA885E64-C5E2-4C22-8C2A-26E68A593F29} = {509BDB5A-5D32-478F-BF27-F0470C18C7C9} - {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {ED301FA5-4E70-460B-A0D4-1D79D135769F} = {593A3114-D1E0-47ED-BC37-58E08886175B} - {21180442-A6A5-4239-A2AD-33FF5BB80E72} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {E33ADF54-4D35-49B7-BDA6-412587CA39FF} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C} = {FBD326D3-E59C-433E-A88E-14E179E3093D} - {D55A7D3B-9C24-4029-BC03-41B28AD11DB6} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {2E172027-1B85-474E-A238-21B2DBDB895F} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {62760E2C-D3D6-4824-997F-35033E6EB92C} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {965C85E2-D94E-43DE-BFC2-B9D157242EBB} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {7D083C64-FF32-43C4-A82C-32C4A4EC1414} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {DE2E92D3-929F-40E7-B8D0-502A57170A3E} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {0B139C21-8AFD-41CD-82FE-36E64FDEDE50} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {3870E8F1-B269-425D-8B03-58835FD53610} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {519A99D2-8094-48EC-A888-C0B4E017A4C1} = {FBD326D3-E59C-433E-A88E-14E179E3093D} - {E946A129-34ED-4069-B44E-EC7B98751006} = {FBD326D3-E59C-433E-A88E-14E179E3093D} - {F3DA1941-3610-48F3-901A-E2E873979BFD} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {EA23C277-97E4-4FBC-A53B-37048813E14F} = {DA885E64-C5E2-4C22-8C2A-26E68A593F29} - {0FF64D9E-98D7-46B1-90FB-C0364C76D65A} = {21180442-A6A5-4239-A2AD-33FF5BB80E72} - {D375233D-8AAC-4234-BC0D-3D103C600C19} = {21180442-A6A5-4239-A2AD-33FF5BB80E72} - {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF} - {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1} = {13EDB361-AF88-4F89-B4AB-46622BCCBC37} - {E893C913-98A0-4BB3-A32B-3871BE3C5C53} = {880E8263-AECC-4793-BD28-7CD03650D124} - {761C3313-A669-465F-A384-9E118FCE4F89} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {13EDB361-AF88-4F89-B4AB-46622BCCBC37} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {880E8263-AECC-4793-BD28-7CD03650D124} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF} - {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {FDF1C618-4C68-485E-A881-4FFF04EDF6E8} = {5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48} - {C056C688-8FFC-42BC-B4EA-EF3808A8A12C} = {59DA3D5F-9E39-4173-8C31-126967CC189F} - {428CDAF3-957A-4017-82EA-70737F205546} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {DB93B639-899D-4B2C-AF8A-47B4BC6B3776} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} - {85BCA106-4A6F-4BEE-A748-E61A24D12DBD} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826} = {13EDB361-AF88-4F89-B4AB-46622BCCBC37} - {5A163042-B03A-4063-85FF-22D4C5BB5B90} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {84EFF9E1-6852-458F-8D57-62E3F084EA0F} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} - {427822F2-7A20-4E3A-B45C-43CE855003C1} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} - {99067BDF-2C6A-47F8-913D-3FF9F2A69F98} = {880E8263-AECC-4793-BD28-7CD03650D124} - {A4DE46BD-1FA4-494B-80DA-6EB370686F17} = {880E8263-AECC-4793-BD28-7CD03650D124} - {7909A736-6C1E-4622-9BE7-37EF0839FA05} = {880E8263-AECC-4793-BD28-7CD03650D124} - {71E02AFA-06A0-4527-923C-6666B3D66542} = {880E8263-AECC-4793-BD28-7CD03650D124} - {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} - {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} - {374B7E56-A815-4F56-A4C2-6094B5A97EE7} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {2971858E-2CCA-4688-B8CA-84F130AD5AA9} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {2503F67B-63BB-4364-8B31-1DD3049C92B7} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {88BC8170-9123-48C0-A914-11D3CE805196} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {316B1D0A-9CF7-4E5C-A39A-8A389B075A19} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {2F4986D6-3F56-4C05-8A1D-399594F96093} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {E72E105D-B15F-4D69-9A13-CAA49D4889D6} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} - EndGlobalSection -EndGlobal diff --git a/NuGet.Config b/NuGet.Config deleted file mode 100644 index 3f0e00340..000000000 --- a/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ -๏ปฟ - - - - - \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 41afb2634..000000000 --- a/README.md +++ /dev/null @@ -1,144 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -[![codecov](https://codecov.io/gh/masastack/MASA.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/MASA.Contrib) - -# MASA.Contrib - -The purpose of MASA.Contrib is based on [MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [MASA Stack](https://github.com/masastack) and [MASA Labs](https://github.com/masalabs) projects. - -## Structure - -```c# -MASA.Contrib -โ”œโ”€โ”€ solution items -โ”‚ โ”œโ”€โ”€ nuget.config -โ”œโ”€โ”€ src -โ”‚ โ”œโ”€โ”€ BasicAbility -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.BasicAbility.Dcc ConfigurationAPI -โ”‚ โ”œโ”€โ”€ Configuration -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Configuration -โ”‚ โ”œโ”€โ”€ Data -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF Unit of work -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Data.Contracts.EF Protocol EF version -โ”‚ โ”œโ”€โ”€ DDD -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain In-process and cross-process support -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF -โ”‚ โ”œโ”€โ”€ Dispatcher -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events In-process event -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.Dapr -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF Cross-process event -โ”‚ โ”œโ”€โ”€ ReadWriteSpliting -โ”‚ โ”‚ โ””โ”€โ”€ CQRS -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.ReadWriteSpliting.CQRS CQRS -โ”‚ โ”œโ”€โ”€ Service -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Service.MinimalAPIs Best practices for [MinimalAPI] -โ”œโ”€โ”€ test -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF.Tests -``` - -## Feature - -### 1. MinimalAPI - -What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[Usage introduction](/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md) - -> Advantage๏ผš -> -> 1. Classify APIs and add them to different Services to make the Service structure clearer and get rid of running account programming - -### 2. EventBus - -[Usage introduction](/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md) - -> Advantage๏ผš -> -> 1. Arrangement of Handler -> 2. Implement [Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) -> 3. Middleware -> 4. Transaction - -> Effect๏ผš -> -> 1. Event and Handler decoupling -> 2. Arrangement of Handler -> 3. Implement [Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) -> 4. Middleware -> 5. Transaction - -### 3. CQRS - -what is[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[Usage introduction](/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md) - -### 4. IntegrationEventBus - -Realize cross-process events based on Daprใ€‚[Usage introduction](/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md) - -> Advantage๏ผšUse the same transaction to commit the user-defined context and the log to ensure atomicity and consistency - -### 5. DomainEventBus - -[Usage introduction](/src/DDD/MASA.Contrib.DDD.Domain/README.md) - -> Advantage๏ผš -> -> 1. CQRS -> 2. Domain Service -> 3. Support domain events (in-process), integrated domain events (cross-process) -> 4. Support the unified sending of field events after being pushed onto the stack - -### 6. DDD - -[DDD](https://www.likecs.com/default/index/show?id=93970) // todo - - -### 7. Contracts.EF - -Protocol based on EF implementation๏ผŒ[Usage introduction](/Data/MASA.Contrib.Data.Contracts.EF/README.md) - -> Advantage๏ผš -> -> 1. Filter deleted information when querying -> 2. Soft delete - -```C# -Install-Package MASA.Contrib.Data.Contracts.EF -``` - -```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => - { - dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - dbOptions.UseSoftDelete(builder.Services); - }); -}); - -``` - -> When the entity inherits ISoftware and is deleted, change the delete state to the modified state, and cooperate with the custom Remove operation to achieve soft deletion -> Do not query the data marked as soft deleted when querying -> When combined with EventBus, the transaction is opened after the first CUD, and the transaction rollback is supported when the entire Handler is abnormal. - -### 8. MASA.Contrib.Configuration - -Redefine Configuration, support the management of Local and ConfigurationAPI nodes, combine IOptions and IOptionsMonitor to complete configuration acquisition and configuration update subscription [Local Usage introduction](src/Configuration/MASA.Contrib.Configuration/README.md) ใ€[Dcc Usage introduction](src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md) - -## Unit testing rules - -To ensure the reliability of the entire source code, the unit test coverage is at least 90% - -## โ˜€๏ธ License agreement - -[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) diff --git a/README.zh-CN.md b/README.zh-CN.md deleted file mode 100644 index 7f3210bf1..000000000 --- a/README.zh-CN.md +++ /dev/null @@ -1,144 +0,0 @@ -๏ปฟไธญ | [EN](README.md) - -[![codecov](https://codecov.io/gh/masastack/MASA.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/MASA.Contrib) - -# MASA.Contrib - -MASA.Contribๆ˜ฏๅŸบไบŽ[MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks)ๆไพ›ๅผ€ๆ”พ, ็คพๅŒบ้ฉฑๅŠจ็š„ๅฏ้‡็”จ็ป„ไปถ๏ผŒ็”จไบŽๆž„ๅปบ็ฝ‘ๆ ผๅบ”็”จ็จ‹ๅบใ€‚่ฟ™ไบ›็ป„ไปถๅฐ†่ขซ[MASA Stack](https://github.com/masastack)ๅ’Œ[MASA Labs](https://github.com/masalabs)็ญ‰้กน็›ฎไฝฟ็”จใ€‚ - -## ็ป“ๆž„ - -```c# -MASA.Contrib -โ”œโ”€โ”€ solution items -โ”‚ โ”œโ”€โ”€ nuget.config -โ”œโ”€โ”€ src -โ”‚ โ”œโ”€โ”€ BasicAbility -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.BasicAbility.Dcc ConfigurationAPI -โ”‚ โ”œโ”€โ”€ Configuration -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Configuration -โ”‚ โ”œโ”€โ”€ Data -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF ๅทฅไฝœๅ•ๅ…ƒ -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Data.Contracts.EF ่ง„็บฆEF็‰ˆ -โ”‚ โ”œโ”€โ”€ DDD -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain ่ฟ›็จ‹ๅ†…ใ€่ทจ่ฟ›็จ‹้ƒฝๆ”ฏๆŒ -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF -โ”‚ โ”œโ”€โ”€ Dispatcher -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events ่ฟ›็จ‹ๅ†…ไบ‹ไปถ -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.Dapr -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF ่ทจ่ฟ›็จ‹ไบ‹ไปถ -โ”‚ โ”œโ”€โ”€ ReadWriteSpliting -โ”‚ โ”‚ โ””โ”€โ”€ CQRS -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.ReadWriteSpliting.CQRS CQRS -โ”‚ โ”œโ”€โ”€ Service -โ”‚ โ”‚ โ””โ”€โ”€ MASA.Contrib.Service.MinimalAPIs MinimalAPIๆœ€ไฝณๅฎž่ทต -โ”œโ”€โ”€ test -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.BenchmarkDotnetTest -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests -โ”‚ โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.Events.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Data.UoW.EF.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Tests -โ”‚ โ”œโ”€โ”€ MASA.Contrib.DDD.Domain.Repository.EF.Tests -``` - -## ็‰นๆ€ง - -### 1. MinimalAPI - -ไป€ไนˆๆ˜ฏ[MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[็”จๆณ•ไป‹็ป](/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md) - -> ไผ˜ๅŠฟ๏ผš -> -> 1. ๅฏนAPI่ฟ›่กŒๅˆ†็ฑปๆทปๅŠ ๅˆฐไธๅŒ็š„Service๏ผŒไฝฟๅพ—Service็ป“ๆž„ๆ›ดๆธ…ๆ™ฐ๏ผŒๆ‘†่„ฑๆตๆฐด่ดฆๅผ็ผ–็จ‹ - -### 2. EventBus - -[็”จๆณ•ไป‹็ป](/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md) - -> ไผ˜ๅŠฟ๏ผš -> -> 1. ๅฏนHandler็š„็ผ–ๆŽ’ -> 2. ๅฎž็Žฐ[Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) -> 3. Middleware -> 4. Transaction - -> ไฝœ็”จ๏ผš -> -> 1. EventไธŽHandler่งฃ่€ฆ -> 2. ๅฏนHandler็š„็ผ–ๆŽ’ -> 3. ๅฎž็Žฐ[Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) -> 4. Middleware -> 5. Transaction - -### 3. CQRS - -ไป€ไนˆๆ˜ฏ[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[็”จๆณ•ไป‹็ป](/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md) - -### 4. IntegrationEventBus - -ๅŸบไบŽDaprๅฎž็Žฐ่ทจ่ฟ›็จ‹็š„ไบ‹ไปถใ€‚[็”จๆณ•ไป‹็ป](/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md) - -> ไผ˜ๅŠฟ๏ผšๅฐ†็”จๆˆท่‡ชๅฎšไน‰ไธŠไธ‹ๆ–‡ไธŽๆ—ฅๅฟ—ไฝฟ็”จๅŒไธ€ไบ‹ๅŠกๆไบค๏ผŒ็กฎไฟๅŽŸๅญๆ€งใ€ไธ€่‡ดๆ€ง - -### 5. DomainEventBus - -[็”จๆณ•ไป‹็ป](/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md) - -> ไผ˜ๅŠฟ๏ผš -> -> 1. CQRS -> 2. ้ข†ๅŸŸๆœๅŠก -> 3. ๆ”ฏๆŒ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ใ€้›†ๆˆ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ทจ่ฟ›็จ‹๏ผ‰ -> 4. ๆ”ฏๆŒๅฏน้ข†ๅŸŸไบ‹ไปถๅ…ˆๅŽ‹ๆ ˆๅŽ็ปŸไธ€ๅ‘้€ - -### 6. DDD - -[DDD](https://www.likecs.com/default/index/show?id=93970) // todo - - -### 7. Contracts.EF - -ๅŸบไบŽEFๅฎž็Žฐ็š„่ง„็บฆ๏ผŒ[็”จๆณ•ไป‹็ป](src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md) - -> ไผ˜ๅŠฟ๏ผš -> -> 1. ๆŸฅ่ฏข็š„ๆ—ถๅ€™่ฟ‡ๆปคๅทฒๅˆ ้™ค็š„ไฟกๆฏ -> 2. ่ฝฏๅˆ ้™ค - -```C# -Install-Package MASA.Contrib.Data.Contracts.EF -``` - -```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => - { - dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - dbOptions.UseSoftDelete(builder.Services);//ๅฏๅŠจ่ฝฏๅˆ ้™ค - }); -}); -``` - -> ๅฝ“ๅฎžไฝ“็ปงๆ‰ฟISoftware๏ผŒไธ”่ขซๅˆ ้™คๆ—ถ๏ผŒๅฐ†ๅˆ ้™ค็Šถๆ€ๆ”นไธบไฟฎๆ”น็Šถๆ€๏ผŒๅนถ้…ๅˆ่‡ชๅฎšไน‰Removeๆ“ไฝœ๏ผŒๅฎž็Žฐ่ฝฏๅˆ ้™ค -> ๆ”ฏๆŒๆŸฅ่ฏข็š„ๆ—ถๅ€™ไธๆŸฅ่ฏข่ขซๆ ‡่ฎฐ่ฝฏๅˆ ้™ค็š„ๆ•ฐๆฎ -> ไธŽEventBus็ป“ๅˆไฝฟ็”จๆ—ถ๏ผŒๅšๅˆฐไบ†็ฌฌไธ€ๆฌกCUDๅŽๅผ€ๅฏไบ‹ๅŠก๏ผŒๅฝ“ๆ•ดไธชHandlerๅ‡บ็Žฐๅผ‚ๅธธๅŽๆ”ฏๆŒไบ‹ๅŠกๅ›žๆปš - -### 8. MASA.Contrib.Configuration - -้‡ๅฎšไน‰Configuration๏ผŒๆ”ฏๆŒLocalใ€ConfigurationAPI่Š‚็‚น็š„็ฎก็†๏ผŒ็ป“ๅˆIOptionsใ€IOptionsMonitorๅฎŒๆˆ้…็ฝฎ็š„่Žทๅ–ไปฅๅŠ้…็ฝฎ็š„ๆ›ดๆ–ฐ่ฎข้˜… [Local็”จๆณ•ไป‹็ป](src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md) ใ€[Dcc็”จๆณ•ไป‹็ป](src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md) - -## ๅ•ๅ…ƒๆต‹่ฏ•่ง„ๅˆ™ - -ไธบ็กฎไฟๆ•ดไธชๆบ็ ็š„ๅฏ้ ๆ€ง๏ผŒๅ•ๅ…ƒๆต‹่ฏ•่ฆ†็›–็Ž‡ๆœ€ไฝŽไธบ90% - -## โ˜€๏ธ ๆŽˆๆƒๅ่ฎฎ - -[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) - diff --git a/packageIcon.png b/packageIcon.png deleted file mode 100644 index 2128805c1995656deba83316df54cb048b9264ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9523 zcmb_?RZtvV)GbMH4=#bh6C4s;1_`dg-8DgjI|&-xAvl9OgA6V~Cpf_dcXt_dkjq#1 z`99qG|A+3bK7046>Z-NRS!`}+MOv^g^lRHswFY3f}@9~klao-i!G2gNH zySuv|;>rBYZCF1_vuMm{8D3#v(PGHyqRWf-NXoq?Adw`92jeIH2nmfxW?&0p_#dT+ zx3(b>BY*zv^D#9$mfVeOJXjK=^U3gPq1}+S$|x44N}p@!BN?7}B8`SKi&IYN5ngBF zs);oV2_(1OIm^#Edrfz-YXcdX-|Vw*Z4GXT-R$P@ziO3WMj(?$PW=2=WRtBPGE|_} ze4Z&^GxPDbL5_#XJFr3vL59^DT;k%E`r;a;t~BRpvXFi85a&LHHhQuG?jeYqXg(N% zX1Vj}zu1OO4347=%wZ&6bN{&Who+=izhS@5s5S#Mg~<;aeG6!4x_4XdYEG1t5@`~n zZtJ3Gp#$1jaQ}pik~VM>C0|64s*TfClT0Di?6!8Bb@B?>GObvJ9`O`xwKi>R6`*pr$jH3Mt+~0m+Z`~xxr$xz zs`F8tHnbc`yd-Qp_XD<&aG*JP^I4oErdGPla&?v_IGPAnO(SBg`!=(b(uUz(OjO6` z2LV}Q_)XmDm;FJ4h0#%lYT3owLuRi=@MKc$vY6r%T=lr5S|&myNjpZgowjAU52|^f zAunQ%vNqqNe^X>=KufzZ2FYI^rYzaEdiT#c5e+8lJ~tg4dbLLqu&s%rEWg~Qv?8Lj zCsH?c-q43xmzkna{NA^4>cZ4a`lMnaVoWUXgBx{39Lw~{V+HkogNUx~Zk)C)%4&-g z&^PGSpZZ%&NM^cZ`6W2AL6^g;9K3O|PCiI%;6?3dMC0=5dvZ&iMVl1sm%qj>n~|NDVW5TB@rL>gdHdndI!Q(w~Xq z(7D_3{PKC062HjdgZh9OiVRI38Gmc#$fNI^a4C9it3tS6C`rnI^ZOO2KcCq8xWM6x-y1QQF1 zqQTh{gC(Rb4HP|0yd3l%t3vRvhye2vGqz8&d-rna2V!bAHU(5r$C43xL`2I@PVH0O zW_Qo)A50E3;Mg`ST5=)wrE4Y6?emv0@QS$4fs)U6F6^ua#a~OJL~*34fjE2IbIYOe zKMrkZX5I(~Tfe{r=}Qf>+tL1sWHRebwU0BsZGK_{or5uIP)HQCd+>P7*z&|MDC+%^ z(XWph@*yZl>?W^MNV6Ud_VRr{$ho$E^y~$Qb5)t?8|~|lc-C`^4k<;-QumuF!qq#1_qE`M4Yz- zYqS-uAhmh5#D;|@sq||W5O`#|l>Nc&7;S$rJ_YrzC2tDJf=%N`E}20w-9MQLJBe&o zuQIqLmyf-GE?tVAE|VPQw^_n~p-|Yy8rc?XVec2RN6SnJr+4q8mq6|uw zxy3Z6=}8<9yP2EzDwUV%X1=w8f+~=~RVE(6%c$%k;}+frK{E#s@^>|RkjqyR_SsDD z7~d@Df;ue$df1rGsWE%Ydff3+PzpW>e4cl10EN%(nv$((67o zuyhhC#-_u=@wnf*waXzFz4f6eNDgg#7|d;r00D#Ae{gTEzXwbQqJv*Pu10Y#s4C*S}TYq^1lFsx_<0XZ8wd{Ifb6xo_r>iYGhkI5f^~Zh>qmgxQD>0PRly zY}r>c9ZXrBZPLWe@0E29c^myc7A%}kfH|zB%|h*&rVv>WiT8u+T{+B}q9HFhXxPS< zWrreQkN;rAERx;hB%Fq}kafz5XL+A@Jkx?BVTg^55F2NH-^-W;G}wzR6m7+oLz@4g zB}nJqxl^m9oL83__^J}t)0^pKh@EgxG(7kRt{-g?`k`C_$)tdfbSc?xA@=H=HL#5n zJ;g*9y=m=k8Ltluk0hRW zI=kiquV(CAa16dH)YKWON_J+l#kJYB+~m?jw%x5@Ay?3E^Ygu-CDV2T94lq4+)*Fj zzd;g{5(JJtfO*-+N#%W6XgZrtbp(Hb|)nix1{$eVDRgST0({ z;&)HVj?G-XyC{O!pR1k_BKXyuR5)k>$KwD?Yb|2D&%L0kd@FvN=VGSOPek<@;x(aX z5s{0eN2}5buRDFSk0!L|A4V6L9H@jqQ)tqzT^S-r7xHybQ`oGRk)SkNHfZ|m(L)4Z z&(+k{)s*GUPk4hg>Lpz5$Vh~xNBv@lY7V@@nRTI{oIc&UYvb)=_+4JEiDOOHQ$W}% zkTH>~sh$a2U*MswcM9_DHzIa2p?qX-`fQXzT0im8g#yRQ7x9aaJ%)DH~0(n3cTbll}O++Dl7% zKEaW<0_`zAUQ=brDOuPLyEVt-pK=#u7fQ*+ow)?}IV_JIj2S#X2E+Go=aUf6gjB_K zos4m(_-FwIVbS&UxmCkk(uX5&Genx*mSSCp6nblNX^nmQW4*>BLqF*B{ACU+KGw_W zH9uW{AvDo8sVsh8NfUP6#ai0}0^s>rwbY;fU0*E3Tv^0!XHi6$RSHwIK}yd}=?9SJ zO3kAOwU6J#tnM6vcsd0UKBFx1d?wt)Dys>3EJ~NKM1# zo7h(!T?{;_lY`*9d!2A%p(Z{vdsx6ov;>QCPTtY0Q?gaWh#c{`UYygWD)rorw`}9E zjEd{GTH6ugf5fj3T`rsUB6hnDm(m>O5*Ojw?WU@49bKBcPdYS#LlV08L8#wXn1=5= zAKtC=kJWIRk_&+Q^CeGNL`?I>nBg9zacT$ef|~@~Bls2^?+6%H2&(jz0~RFx-H`8g zh*~?};F(b=t_vW~Y$oO+sIz5$W%*!p=pkqLCFoqAZ(`JHqje~nL@)@o_u!I!9^T3u z`zd-DD#vI`x%@|4QnfZrtk?E16#gW`=>^43MRbH$)3(EvO38vvnc;(;X;S?E5*o z^@BV0!8$19&VWtv0`kp%eB9$Y>sOniE*>*4j#+Rf`m7#__E@4U0Ket=$W0<`_nG#l za(BmRR&N#ZRnFOHurzJlGac?IIv5Jtc?|gblT;_;& zNnws}Fz%~+^wm>A)y5xPLu+bo)RRq~bA^*jdmeK}&f(dEqNP0)ZAs{ab_V^;7m#YU zKW&#>C*27$Qg%2egQ?NHTllAn0EadZwYAZD%Yphti@rvqzKu<@&&}ooG&85HWxws3 z3vyzTY-i49bc36}%RIQ=KUxBfqF9nz(> zTp0c83<0{(P>8z~fp#1%31?!`&zcN&n4Q@=QK&mT@oY)RE84KIOIdcj>aXYq71V&q zlqv$#vdtr&hXK7s)6!=$gQiN2&zi`P4Z{95(2B{k!S|;RYDyhX5Q)MkFc4c_!j^Lw zqv0)G0K1@){aoVVMDI0qF0jY5GGRPGk+(Vuw$oqN(5GIFz1S7e>JJLZaq`1HhxrV5 zJis9j1M>tF`Mx^8SQBtLDFg8k>#jwDiQUe>J7i$h6?Qdp=^?yZq89b|Q5_W}e`^L< zstmRr-|XvzB~P&AE-8MyV0*?n{G6$MeMQMGRJ%8)uIuD6qJ;33Jr^;IVj@-14!Gr< z`N^*N59jtpC*V~ZgWd?_>BfBr66S<^;tKg>E1wYlQ;#D_vT%+yjN)_t(ot z9_?0#%5TixwHUm8YpOYjEAim>>(%&aDZk#=-bYk^_jLcJC(?WKDFJXtGf{|lIu`oI z$Z421PFie|Xn4w+TLo+*?c|?>0qtU!{Jbw~@GNGs`9N1}-D63Nsjqm)7H=+h^(x=CShwVK0wuOB)Oc|Lr~9%Q$4_^QzZu2h&JAew#WY3_KLw5;obKJJR|I^#m8BEC4bR8oPX=wbKfwKT-{F&gWzkTaLz2@zOM zSMGuijR=uWK7PZ__9m*B1#&Q%WdOX?)~X2BlNWde7=sDdD?BcVNqJ=0p@6A>bM2WO zew>#L?g+S>^>F(P3QEwhPjOgi*wlNiZC)}MM!#s)ugV2o=@+BOzu$1s>iN8AT`g}= z-6o($Ck74B_`7g2-Ok)>7GNzIo za?8^2oamL`n?*Zcqt73@3v=7+6I}1&)oCD@HAa9=SuWRc_r=Fj94w!tH3?joEq?DY zGXfjg&k7dv_5SWq{pRF97>zKmupU-DYIHAxP`b<#UZORrj|%Jhuz$aCK4tOajz}%$ z(2?WcIXzA-kGR#Eb)Ye-DYL^dZ^6KVb{D;Bm}_fxuPdz)dtN|CeBYR-Pn{O@m!KP^ z1WdHSh|7oi#vpZj!CQYk_Xn!LU)g-i1YfT6k=neYCsnU`?ej zz?~!2?s@ZY%b|E|U9_ItQFH)|Kbg$MK;N2anX5J0O8>m^^n6+K$_o)e4eJ=TeDDX^ zKwX{p8Qv~LDU+7m^L!gk*GkF6NK;)XYx6Lg``ba{`tPk~J-)|8o6^tI?wOP%$ z0=coNoB~T2S_nFC<#kHl1|EuhJ<;5UfSv1HW@y>BBLU6^xbf~?*hu$X4)1uEJHdVx z7qhNdXn};1w-J}v>x9X(CdG2{fB3_Mk3z5C$sq2rTxxS#9SH~8Z%h!irs`~b4oF|A zO*-6Tm6ZD;I|Yy1>PHvqZ))_a2VL=rC8VY2@PmJfgVh8%a~bOF`~mNB7F_ukk2C+=B>7|g2Q zld49ewxJ$sxs?~0o`{t>Y%NDQ}5>j&Ry{_n;IT;0TB9L?$#x^ zT0g!%yXI{YwmSv!P`?=dLRbW*IOY1FKi&H`oVGto2dV@Q<6^#EAS<(FNr?geI@ zIy+h0EgNWb35p@mjJUr(tprH=>i89+AQ5(b{4MjW%*FHaA0N0CXQS3YmI$Q#XfNX6|8O-`$Y-*ri1*0<*lTu72_QQLLiKT)%T^E4 zj%H^Z*dDGXF?x~-&F=S?(s0q)#%$A`DkZH1b9UDwJHW+%=r3Gn4||)wF1njVd4w~k zI|mOxO-5%aEW)4dc-Ok-i|eV;_#}pqiD&Hw!rd&=j%RpIO)EfP;7GC)e0ID~$okuA zu#uEoAqvn&1AYI{tSOyp&!QwWQK{pb-=r+phTwRA1Uhn!|sA>sw9sB zvSUA~uTB6ii92hzSK3Fp%BX`-3c1_)u=lNWe^Pf&4P|-qd)(n*D`BNik<$G|8uJ{{ z7a-Li{GZS7iFvqXd~uXeH)<^iEh3cat4UT%jFa;s6G0m*gr!o!LLla;57{c^Bbtbg zZspZMnZ~QJVVCP~z5$6;_f>SA0$%p-A?K8W1SCjo>9l~=<;9}6Ws~a}U|ags++faZ zTF%yA@Et+(=bDefxL(E=lJp8Vy{3PHB{;pQQmhqrSBPJ{`nquglJB)__UN7-yj(po zBgO@=vTnuunwbE@0L&^a`*`i9;42xpMB|krf z|Gg!X-0$!DIPK92d?fd{M@qjZ(($VH^w%OOdR9n$wS8}@J^WR<)(1OLKw3hOG=N5fAY>!7bAZRrr02L0*slU$ znj$!7c`{7vw0|VY-<(PWPoWLI6y9+7DLkX}Px189a6+LFX@y$mA@^_YxO&+W#`>vR z(1zb28vhxVth_Q%yNc;%2!Fum%hQ7zoiLTJgMh0oYf8qia5t--dx4balLdjp2c zW9GMg-FN)9zdmYdJ=~o+2x;VOWpS66&$MhK8j1xUgh|g)2sM4!@e7?f!g?!HTB{$a zR%WWOZi8;LcC=m`71w}C_Yb-r56j_^SkS*a$Me5;>-!Zrb!$xs;UYNqgZ@XC|5%i% znUaG4(adGk%E4T9K~=*EQn$$4ilXVP38^>EM>b*Wr=(-!&TMR%Wnkl?xqGXfYZR9U z(y;Dven7K#>TfZLL9(C8KbfJ{3z8kn(HMj}bP-O#FD`8HM8DPRtT|xmFu|3UkU(CQ zeljc)or70i|7PfJ5?F1Rkyr&_=$OQ8%LUw{YGV{2Kewpc&0_s?zpTgcnd$2pmo=&)5ZRkCw&2m%sqxcH?kEZW63jcPpMo%b)e^c zxnH@<3l;KQ)~XRI#Hj48%BdPrX)h{Npm@gQJ#YZK&nQO@J_Q{1o_vvn0R*!f zzzNKq-RjtLtPjhoTA)h)1A7!TIzczcvte;`P<76SluVIdV!>@{S`w0hj z`MwscZ7B`2E_m*Cij_KSuUS<6L!b!8-u3Brkf`G0&t^KnQX)DOMSST9HWoj{TxGgq zrW{wZ^nV%hV>1c-eP87`=(e4NklO3otA2CT^rZxkAiQyifO?IMwPE$$dpK(q-k06( zG>eqMb1%e6v|z3jIt^xB`?D_N;-kiTld?n;Bu@>)P(0{!+hR?KkD-BaXInecce z;sk}O^}j=l?Mu$-+84f?wF-5dDJ2a^LWrQTZjv|pryR|C)A1ADTx=_aa@)jp2+lWq zRJ)8VmJ7u+$5+3waopZ18`V&(Fx4-=>!~1jJG=HR+4rL?&PHair=vlg^a{1Y0>c*9 zXM9C5bw1l}l?E_p6nUXONyO*`4do66fI%ftbf+;ftkFj z)?8C{HRi(qVkgf8Njk}@{8DMWlC`sSJ8*FyWvq%8?p+S0G%^*<$eg3`yE5a}`%fr2 zKg@y82T(Vbgk&?bGpfa0%0b$1{)JMA!JzqNk>UIiZwX_LTkA3m=bkG@gyi;s@yAQD z{wGd!EI~qNnQZ3u_A5+Aa{R&@T-zn>Vz4|Srx?AGgi=#Xm{^5zG4`Zt>fx2p$Wp+( z^EQ#0!XdZ2uD*wPpph->%cqP5~9!!Ol@(aCj4jV}ZqZ55IB^F)rWnZc+*=o)ZD*P4Os} zZgsYGmBDohUhR(zzZ(hr8;PUz-oC{Bp--~tZ9SNdBg<{rn4Fy!s%g0f=gD6cWcP7m zHBpetgQ|ioPB)5qY9X%2(f<5cW{4Q;a_wCk4%n{s`f5NP*VT_!xJA3)-DBpKO&mVk zn>#_t@slb36jZd;ic?^`TIpKN)y1VOMliKw4t+6PD}6CxgB$070XU!x`tFAv@U6?v zr0|GauJkFlGqFUZ-kvxYMyC?X~ zqNjXq?yjL&-G@w8mNj)lzE|3)a=CNt*rIJDm*@uY&E}!lQ2SZvfdbQrqF~rNJ@<9v z8{9c)8bF)wy|*gy?Sj_)do=5`D)RlvG2@IkSrZYD@UPTj&5^r#%! zUx9pGQO%=20Lp-GvOs} zo-g1CT#mg>FVod7KX!f5f*d(C@&uB;+)bPWFB|}PtH0{hP?}gR^Y)y(d5BWz%3Q&a z0b1wG9{H;)4xzpM(qh8bcJLl*p`(%@g2(wB1+t7!58{0huqheokw!p?t!?y~f1q%y z63B~hBJp0R&N+9C(9)Xag_!{sqH0%CR^WRF{F@3(?A@rzdpfveaC}Jakzg{n*dwpW z-?)>!&iNg1uE~Cxrh#}{)UVO$*`kZ#gpo`D#kRuBj`iYp&wTqcuT=k_`cp((Fb~!f z=Ip&?Jk>> _taskExpandoObjects = new(); - private readonly ConcurrentDictionary>> _taskJsonObjects = new(); - - public ConfigurationApiClient( - IServiceProvider serviceProvider, - IMemoryCacheClient client, - JsonSerializerOptions jsonSerializerOptions, - DccSectionOptions defaultSectionOption, - List? expandSectionOptions) - : base(defaultSectionOption, expandSectionOptions) - { - _serviceProvider = serviceProvider; - _client = client; - _jsonSerializerOptions = jsonSerializerOptions; - } - - public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string environment, string cluster, string appId, - string configObject, Action valueChanged) - { - var key = FomartKey(environment, cluster, appId, configObject); - return GetRawByKeyAsync(key, valueChanged); - } - - public async Task GetAsync(string environment, string cluster, string appId, string configObject, Action valueChanged) - { - var key = FomartKey(environment, cluster, appId, configObject); - - var value = await _taskJsonObjects.GetOrAdd(key, (k) => new Lazy>(async () => - { - var options = new JsonSerializerOptions(_jsonSerializerOptions); - options.EnableDynamicTypes(); - - var result = await GetRawByKeyAsync(k, (value) => - { - var result = JsonSerializer.Deserialize(value, options); - - var newValue = new Lazy>(() => Task.FromResult((object)result!)); - _taskJsonObjects.AddOrUpdate(k, newValue, (_, _) => newValue); - valueChanged?.Invoke(result!); - }); - if (typeof(T).GetInterfaces().Any(type => type == typeof(IConvertible))) - { - if (result.ConfigurationType == ConfigurationTypes.Text) - return Convert.ChangeType(result.Raw, typeof(T)); - - throw new FormatException(result.Raw); - } - - return JsonSerializer.Deserialize(result.Raw, options) ?? throw new ArgumentException(nameof(configObject)); - })).Value; - - return (T)value; - } - - public async Task GetDynamicAsync(string environment, string cluster, string appId, string configObject, - Action valueChanged) - { - var key = FomartKey(environment, cluster, appId, configObject); - - var value = _taskExpandoObjects.GetOrAdd(key, (k) => new Lazy>(async () => - { - var options = new JsonSerializerOptions(_jsonSerializerOptions); - options.EnableDynamicTypes(); - - var raw = await GetRawByKeyAsync(k, (value) => - { - var result = JsonSerializer.Deserialize(value, options); - var newValue = new Lazy>(() => Task.FromResult(result)!); - _taskExpandoObjects.AddOrUpdate(k, newValue!, (_, _) => newValue!); - valueChanged?.Invoke(result!); - }); - - return JsonSerializer.Deserialize(raw.Raw, options) ?? throw new ArgumentException(nameof(configObject)); - })).Value; - - return await value; - } - - public async Task GetDynamicAsync(string key) - { - if (string.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key)); - - var configuration = _serviceProvider.GetRequiredService(); - key = key.Replace(".", ConfigurationPath.KeyDelimiter); - return await Task.FromResult(Format(configuration.GetSection(key))); - } - - private async Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawByKeyAsync(string key, Action valueChanged) - { - var raw = await _client.GetAsync(key, (value) => - { - var result = FormatRaw(value); - valueChanged?.Invoke(result.Raw); - }); - - return FormatRaw(raw); - } - - private (string Raw, ConfigurationTypes ConfigurationType) FormatRaw(string? raw) - { - if (raw == null) - throw new ArgumentException("configObject invalid"); - - var result = JsonSerializer.Deserialize(raw, _jsonSerializerOptions); - if (result == null || result.ConfigFormat == 0) - throw new ArgumentException("configObject invalid"); - - switch (result.ConfigFormat) - { - case ConfigFormats.Json: - return (result.Content!, ConfigurationTypes.Json); - - case ConfigFormats.Text: - return (result.Content!, ConfigurationTypes.Text); - - case ConfigFormats.Properties: - var properties = PropertyConfigurationParser.Parse(result.Content!, _jsonSerializerOptions); - if (properties == null) - throw new ArgumentException("configObject invalid"); - - return (JsonSerializer.Serialize(properties, _jsonSerializerOptions), ConfigurationTypes.Properties); - - default: - throw new NotSupportedException("Unsupported configuration type"); - } - } - - private string FomartKey(string environment, string cluster, string appId, string configObject) - => $"{GetEnvironment(environment)}-{GetCluster(cluster)}-{GetAppId(appId)}-{GetConfigObject(configObject)}".ToLower(); - - private dynamic Format(IConfigurationSection section) - { - var childrenSections = section.GetChildren(); - if (!section.Exists() || !childrenSections.Any()) - { - return section.Value; - } - else - { - var result = new ExpandoObject(); - var parent = result as IDictionary; - foreach (var child in childrenSections) - { - parent[child.Key] = Format(child); - } - return result; - } - } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs deleted file mode 100644 index f8f5036a9..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc; - -public class ConfigurationApiManage : ConfigurationAPIBase, IConfigurationApiManage -{ - private readonly ICallerProvider _callerProvider; - - public ConfigurationApiManage( - ICallerProvider callerProvider, - DccSectionOptions defaultSectionOption, - List? expandSectionOptions) - : base(defaultSectionOption, expandSectionOptions) - { - _callerProvider = callerProvider; - } - - public async Task UpdateAsync(string environment, string cluster, string appId, string configObject, object value) - { - var requestUri = $"open-api/releasing/{GetEnvironment(environment)}/{GetCluster(cluster)}/{GetAppId(appId)}/{GetConfigObject(configObject)}?secret={GetSecret(appId)}"; - var result = await _callerProvider.PutAsync(requestUri, value, default); - - // 299 is the status code when throwing a UserFriendlyException in masa.framework - if ((int)result.StatusCode == 299 || !result.IsSuccessStatusCode) - { - var error = await result.Content.ReadAsStringAsync(); - throw new HttpRequestException(error); - } - } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs deleted file mode 100644 index fbd4b6c6e..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal; - -internal enum ConfigFormats -{ - Properties = 1, - Text, - Json -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs deleted file mode 100644 index cbbd6b8c5..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal; - -public class ConfigurationAPIBase -{ - private readonly DccSectionOptions _defaultSectionOption; - private readonly List _expandSectionOptions; - - protected ConfigurationAPIBase(DccSectionOptions defaultSectionOption, List? expandSectionOptions) - { - _defaultSectionOption = defaultSectionOption; - _expandSectionOptions = expandSectionOptions ?? new(); - } - - protected string GetSecret(string appId) - { - if (_defaultSectionOption.AppId == GetAppId(appId)) - return _defaultSectionOption.Secret ?? ""; - - var option = _expandSectionOptions.FirstOrDefault(x => x.AppId == appId); - if (option == null) - throw new ArgumentNullException(nameof(appId)); - - return option.Secret ?? ""; - } - - protected string GetEnvironment(string environment) - => !string.IsNullOrEmpty(environment) ? environment : _defaultSectionOption.Environment!; - - protected string GetCluster(string cluster) - => !string.IsNullOrEmpty(cluster) ? cluster : _defaultSectionOption.Cluster!; - - protected string GetAppId(string appId) - => !string.IsNullOrEmpty(appId) ? appId : _defaultSectionOption.AppId!; - - protected string GetConfigObject(string configObject) - => !string.IsNullOrEmpty(configObject) ? configObject : throw new ArgumentNullException(nameof(configObject)); -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs deleted file mode 100644 index d352c723b..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Constants.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal; - -internal class Constants -{ - internal const string DEFAULT_CLIENT_NAME = "masa.plugins.caching.dcc"; - - internal const string DEFAULT_SUBSCRIBE_KEY_PREFIX = "masa.dcc:"; - - internal const string DEFAULT_ENVIRONMENT_NAME = "ASPNETCORE_ENVIRONMENT"; - - internal const string DATA_DICTIONARY_SECTION_NAME = "DataDictionary"; -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs deleted file mode 100644 index 40158fc81..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal; - -internal class DccConfigurationRepository : AbstractConfigurationRepository -{ - private readonly IConfigurationApiClient _client; - - public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; - - private readonly ConcurrentDictionary> _dictionaries = new(); - - private readonly ConcurrentDictionary _configObjectConfigurationTypeRelations = new(); - - public DccConfigurationRepository( - IEnumerable sectionOptions, - IConfigurationApiClient client, - ILoggerFactory loggerFactory) - : base(loggerFactory) - { - _client = client; - foreach (var sectionOption in sectionOptions) - { - Initialize(sectionOption).ConfigureAwait(false).GetAwaiter().GetResult(); - } - } - - private async Task Initialize(DccSectionOptions sectionOption) - { - foreach (var configObject in sectionOption.ConfigObjects) - { - string key = $"{sectionOption.Environment!}-{sectionOption.Cluster!}-{sectionOption.AppId}-{configObject}".ToLower(); - var result = await _client.GetRawAsync(sectionOption.Environment!, sectionOption.Cluster!, sectionOption.AppId, configObject, (val) => - { - if (_configObjectConfigurationTypeRelations.TryGetValue(key, out var configurationType)) - { - _dictionaries[key] = FormatRaw(sectionOption.AppId, configObject, val, configurationType); - FireRepositoryChange(SectionType, Load()); - } - }); - - _configObjectConfigurationTypeRelations.TryAdd(key, result.ConfigurationType); - _dictionaries[key] = FormatRaw(sectionOption.AppId, configObject, result.Raw, result.ConfigurationType); - } - } - - private IDictionary FormatRaw(string appId, string configObject, string? raw, ConfigurationTypes configurationType) - { - if (raw == null) - return new Dictionary(); - - switch (configurationType) - { - case ConfigurationTypes.Json: - return SecondaryFormat(appId, configObject, JsonConfigurationParser.Parse(raw)); - case ConfigurationTypes.Properties: - return SecondaryFormat(appId, configObject, JsonSerializer.Deserialize>(raw)!); - case ConfigurationTypes.Text: - return new Dictionary() - { - { $"{appId}{ConfigurationPath.KeyDelimiter}{DATA_DICTIONARY_SECTION_NAME}{ConfigurationPath.KeyDelimiter}{configObject}" , raw ?? "" } - }; - default: - throw new NotSupportedException(nameof(configurationType)); - } - } - - private IDictionary SecondaryFormat( - string appId, - string configObject, - IDictionary data) - { - var dictionary = new Dictionary(); - foreach (var item in data) - { - dictionary[$"{appId}{ConfigurationPath.KeyDelimiter}{configObject}{ConfigurationPath.KeyDelimiter}{item.Key}"] = item.Value; - } - return dictionary; - } - - public override Properties Load() - { - Dictionary properties = new(); - foreach (var item in _dictionaries) - { - foreach (var key in item.Value.Keys) - { - properties[key] = item.Value[key] ?? string.Empty; - } - } - return new Properties(properties); - } - -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs deleted file mode 100644 index 039bee5b4..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal; - -internal class DccFactory -{ - public static IConfigurationApiClient CreateClient( - IServiceProvider serviceProvider, - IMemoryCacheClient client, - JsonSerializerOptions jsonSerializerOptions, - DccSectionOptions defaultSectionOption, - List? expandSectionOptions) - { - return new ConfigurationApiClient(serviceProvider, client, jsonSerializerOptions, defaultSectionOption, expandSectionOptions); - } - - public static IConfigurationApiManage CreateManage( - ICallerFactory callerFactory, - DccSectionOptions defaultSectionOption, - List? expandSectionOptions) - => new ConfigurationApiManage(callerFactory.CreateClient(), defaultSectionOption, expandSectionOptions); -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs deleted file mode 100644 index 760b9cc23..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.BasicAbility.Dcc.Internal.Model; - -internal class Property -{ - public string Key { get; set; } = default!; - - public string Value { get; set; } = default!; -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs deleted file mode 100644 index b94b53372..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.BasicAbility.Dcc.Internal.Model; - -internal class PublishRelease -{ - public ConfigFormats ConfigFormat { get; set; } - - public string? Content { get; set; } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs deleted file mode 100644 index b23f71761..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal.Parser; - -/// -/// https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs -/// -internal sealed class JsonConfigurationParser -{ - private JsonConfigurationParser() - { - } - - private readonly Dictionary _data = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly Stack _paths = new Stack(); - - public static IDictionary Parse(string json) - => new JsonConfigurationParser().ParseJson(json); - - private IDictionary ParseJson(string json) - { - var jsonDocumentOptions = new JsonDocumentOptions - { - CommentHandling = JsonCommentHandling.Skip, - AllowTrailingCommas = true, - }; - - var doc = JsonDocument.Parse(json, jsonDocumentOptions); - - if (doc.RootElement.ValueKind != JsonValueKind.Object) - { - throw new FormatException($"[{doc.RootElement.ValueKind}]Invalid top level JsonElement."); - } - - VisitElement(doc.RootElement); - - return _data; - } - - private void VisitElement(JsonElement element) - { - var isEmpty = true; - - foreach (JsonProperty property in element.EnumerateObject()) - { - isEmpty = false; - EnterContext(property.Name); - VisitValue(property.Value); - ExitContext(); - } - - if (isEmpty && _paths.Count > 0) - { - _data[_paths.Peek()] = ""; - } - } - - private void VisitValue(JsonElement value) - { - Debug.Assert(_paths.Count > 0); - - switch (value.ValueKind) - { - case JsonValueKind.Object: - VisitElement(value); - break; - - case JsonValueKind.Array: - int index = 0; - foreach (JsonElement arrayElement in value.EnumerateArray()) - { - EnterContext(index.ToString()); - VisitValue(arrayElement); - ExitContext(); - index++; - } - - break; - - case JsonValueKind.Number: - case JsonValueKind.String: - case JsonValueKind.True: - case JsonValueKind.False: - case JsonValueKind.Null: - string key = _paths.Peek(); - if (_data.ContainsKey(key)) - { - throw new FormatException($"[{key}]key is duplicated."); - } - - _data[key] = value.ToString(); - break; - - default: - throw new FormatException($"[{value.ValueKind}]Unsupported json token."); - } - } - - private void EnterContext(string context) => - _paths.Push(_paths.Count > 0 ? _paths.Peek() + ConfigurationPath.KeyDelimiter + context : context); - - private void ExitContext() => _paths.Pop(); -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs deleted file mode 100644 index fb05155cd..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Internal.Parser; - -internal class PropertyConfigurationParser -{ - public static IDictionary? Parse(string raw, JsonSerializerOptions serializerOption) - => JsonSerializer.Deserialize>(raw, serializerOption)?.ToDictionary(k => k.Key, v => v.Value); -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj deleted file mode 100644 index 7830d62c8..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MASA.Contrib.BasicAbility.Dcc.csproj +++ /dev/null @@ -1,23 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - - - - - diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs deleted file mode 100644 index ac6c63bb9..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs +++ /dev/null @@ -1,204 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc; - -public static class MasaConfigurationExtensions -{ - public static IMasaConfigurationBuilder UseDcc( - this IMasaConfigurationBuilder builder, - IServiceCollection services, - Action? jsonSerializerOptions = null, - Action? callerOptions = null) - => builder.UseDcc(services, "Appsettings", jsonSerializerOptions, callerOptions); - - public static IMasaConfigurationBuilder UseDcc( - this IMasaConfigurationBuilder builder, - IServiceCollection services, - string defaultSectionName, - Action? jsonSerializerOptions = null, - Action? callerOptions = null) - { - if (!builder.GetSectionRelations().TryGetValue(defaultSectionName, out IConfiguration? configuration)) - throw new ArgumentNullException("Failed to obtain Dcc configuration, check whether the current section is configured with Dcc"); - - var configurationSection = configuration.GetSection("DccOptions"); - var dccOptions = configurationSection.Get(); - - List expandSections = new(); - var configurationExpandSection = configuration.GetSection("ExpandSections"); - if (configurationExpandSection.Exists()) - { - configurationExpandSection.Bind(expandSections); - } - - return builder.UseDcc(services, () => dccOptions, option => - { - option.Environment = configuration["Environment"]; - option.Cluster = configuration["Cluster"]; - option.AppId = configuration["AppId"]; - option.ConfigObjects = configuration.GetSection("ConfigObjects").Get>(); - option.Secret = configuration["Sectet"]; - }, option => option.ExpandSections = expandSections, jsonSerializerOptions, callerOptions); - } - - public static IMasaConfigurationBuilder UseDcc( - this IMasaConfigurationBuilder builder, - IServiceCollection services, - Func configureOptions, - Action defaultSectionOptions, - Action? expansionSectionOptions = null, - Action? jsonSerializerOptions = null, - Action? callerOptions = null) - { - if (services.Any(service => service.ImplementationType == typeof(DccConfigurationProvider))) - return builder; - - services.AddSingleton(); - - var config = GetDccConfigurationOption(configureOptions, defaultSectionOptions, expansionSectionOptions); - - var jsonSerializerOption = new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true - }; - jsonSerializerOptions?.Invoke(jsonSerializerOption); - services.AddCaller(options => - { - if (callerOptions == null) - { - options.UseHttpClient(() - => new MasaHttpClientBuilder(DEFAULT_CLIENT_NAME, string.Empty, opt => opt.BaseAddress = new Uri(config.DccConfigurationOption.ManageServiceAddress)) - ); - } - else - { - callerOptions.Invoke(options); - } - }); - - services.AddMasaRedisCache(DEFAULT_CLIENT_NAME, config.DccConfigurationOption.RedisOptions).AddSharedMasaMemoryCache(config.DccConfigurationOption.SubscribeKeyPrefix ?? DEFAULT_SUBSCRIBE_KEY_PREFIX); - - TryAddConfigurationApiClient(services, config.DefaultSectionOption, config.ExpansionSectionOptions, jsonSerializerOption); - TryAddConfigurationApiManage(services, config.DefaultSectionOption, config.ExpansionSectionOptions); - - var sectionOptions = new List() - { - config.DefaultSectionOption - }.Concat(config.ExpansionSectionOptions); - - var configurationApiClient = services.BuildServiceProvider().GetRequiredService(); - var loggerFactory = services.BuildServiceProvider().GetRequiredService(); - builder.AddRepository(new DccConfigurationRepository(sectionOptions, configurationApiClient, loggerFactory)); - return builder; - } - - public static IServiceCollection TryAddConfigurationApiClient(IServiceCollection services, - DccSectionOptions defaultSectionOption, - List expansionSectionOptions, - JsonSerializerOptions jsonSerializerOption) - { - services.TryAddSingleton(serviceProvider => - { - var client = serviceProvider.GetRequiredService() - .CreateClient(DEFAULT_CLIENT_NAME); - - if (client == null) - throw new ArgumentNullException(nameof(client)); - - return DccFactory.CreateClient( - serviceProvider, - client, - jsonSerializerOption, - defaultSectionOption, - expansionSectionOptions); - }); - return services; - } - - public static IServiceCollection TryAddConfigurationApiManage(IServiceCollection services, - DccSectionOptions defaultSectionOption, - List expansionSectionOptions) - { - services.TryAddSingleton(serviceProvider => - { - var callerFactory = serviceProvider.GetRequiredService(); - return DccFactory.CreateManage(callerFactory, defaultSectionOption, expansionSectionOptions); - }); - return services; - } - - private static (DccSectionOptions DefaultSectionOption, List ExpansionSectionOptions, DccConfigurationOptions DccConfigurationOption) GetDccConfigurationOption( - Func configureOptions, - Action defaultSectionOptions, - Action? expansionSectionOptions = null) - { - var dccConfigurationOption = configureOptions(); - if (dccConfigurationOption == null) - throw new ArgumentNullException(nameof(configureOptions)); - - if (string.IsNullOrEmpty(dccConfigurationOption.ManageServiceAddress)) - throw new ArgumentNullException(nameof(dccConfigurationOption.ManageServiceAddress)); - - if (dccConfigurationOption.RedisOptions == null) - throw new ArgumentNullException(nameof(dccConfigurationOption.RedisOptions)); - - if (dccConfigurationOption.RedisOptions.Servers == null || dccConfigurationOption.RedisOptions.Servers.Count == 0 || dccConfigurationOption.RedisOptions.Servers.Any(service => string.IsNullOrEmpty(service.Host) || service.Port <= 0)) - throw new ArgumentNullException(nameof(dccConfigurationOption.RedisOptions.Servers)); - - if (defaultSectionOptions == null) - throw new ArgumentNullException(nameof(defaultSectionOptions)); - - var defaultSectionOption = new DccSectionOptions(); - defaultSectionOptions.Invoke(defaultSectionOption); - - if (string.IsNullOrEmpty(defaultSectionOption.AppId)) - throw new ArgumentNullException("AppId cannot be empty"); - - if (defaultSectionOption.ConfigObjects == null || !defaultSectionOption.ConfigObjects.Any()) - throw new ArgumentNullException("ConfigObjects cannot be empty"); - - if (string.IsNullOrEmpty(defaultSectionOption.Cluster)) - defaultSectionOption.Cluster = "Default"; - if (string.IsNullOrEmpty(defaultSectionOption.Environment)) - defaultSectionOption.Environment = GetDefaultEnvironment(); - - var dccCachingOption = new DccExpandSectionOptions(); - expansionSectionOptions?.Invoke(dccCachingOption); - List expansionOptions = new(); - foreach (var expansionOption in dccCachingOption.ExpandSections ?? new()) - { - if (string.IsNullOrEmpty(expansionOption.Environment)) - expansionOption.Environment = defaultSectionOption.Environment; - if (string.IsNullOrEmpty(expansionOption.Cluster)) - expansionOption.Cluster = defaultSectionOption.Cluster; - - if (expansionOption.ConfigObjects == null || !expansionOption.ConfigObjects.Any()) - throw new ArgumentNullException("ConfigObjects in the extension section cannot be empty"); - - if (expansionOption.AppId == defaultSectionOption.AppId || - expansionOptions.Any(section => section.AppId == expansionOption.AppId)) - throw new ArgumentNullException("The current section already exists, no need to mount repeatedly"); - - expansionOptions.Add(expansionOption); - } - return (defaultSectionOption, expansionOptions, dccConfigurationOption); - } - - private static ICachingBuilder AddSharedMasaMemoryCache(this ICachingBuilder builder, string subscribeKeyPrefix) - { - builder.AddMasaMemoryCache(options => - { - options.SubscribeKeyType = SubscribeKeyTypes.SpecificPrefix; - options.SubscribeKeyPrefix = subscribeKeyPrefix; - }); - - return builder; - } - - private static string GetDefaultEnvironment() - => System.Environment.GetEnvironmentVariable(DEFAULT_ENVIRONMENT_NAME) ?? - throw new ArgumentNullException("Error getting environment information, please make sure the value of ASPNETCORE_ENVIRONMENT has been configured"); - - private class DccConfigurationProvider - { - - } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs deleted file mode 100644 index 6ee6baf4f..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Options; - -public class DccConfigurationOptions -{ - public RedisConfigurationOptions RedisOptions { get; set; } - - public string ManageServiceAddress { get; set; } = default!; - - /// - /// The prefix of Dcc PubSub, it is not recommended to modify - /// - public string? SubscribeKeyPrefix { get; set; } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs deleted file mode 100644 index e9629637c..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Options; - -public class DccExpandSectionOptions -{ - /// - /// Expansion section information - /// - public List? ExpandSections { get; set; } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs deleted file mode 100644 index fc905907e..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Options; - -public class DccSectionOptions -{ - /// - /// The environment name. - /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty - /// - public string? Environment { get; set; } = null; - - /// - /// The cluster name. - /// - public string? Cluster { get; set; } - - /// - /// The app id. - /// - public string AppId { get; set; } = default!; - - public List ConfigObjects { get; set; } = default!; - - public string? Secret { get; set; } -} diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md deleted file mode 100644 index d74017c3a..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.md +++ /dev/null @@ -1,164 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## MASA.Contrib.BasicAbility.Dcc - -Effect: - -Extend the ability of IConfiguration to manage remote configuration through Dcc. - -```c# -IConfiguration -โ”œโ”€โ”€ Local Local node (fixed) -โ”œโ”€โ”€ ConfigurationAPI Remote node (fixed Dcc to expand its capacity) -โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms Custom node -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name Parameter Name -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary Dictionary (fixed) The type of Text in DCC is mounted here -``` - -Example๏ผš - -```C# -Install-Package MASA.Contrib.Configuration -Install-Package MASA.Contrib.BasicAbility.Dcc //Provides the ability to remotely configure -``` - -appsettings.json -``` -{ - //Dcc configuration, extended Configuration capabilities, support remote configuration - "DccOptions": { - "ManageServiceAddress": "http://localhost:8890", - "RedisOptions": { - "Servers": [ - { - "Host": "localhost", - "Port": 8889 - } - ], - "DefaultDatabase": 0, - "Password": "" - } - }, - "AppId": "Replace-With-Your-AppId", - "Environment": "Development", - "ConfigObjects": [ "Platforms" ], //The name of the object to be mounted, the Platforms configuration will be mounted here under the ConfigurationAPI: node - "Secret": "", //Dcc App key - "Cluster": "Default" -} - -``` - -```C# -builder.AddMasaConfiguration(configurationBuilder => -{ - configurationBuilder.UseDcc(builder.Services);//Use Dcc - - options.Mapping(SectionTypes.Local, "Appsettings", ""); //Map CustomDccSectionOptions to the Appsettings node under Local -}); - -/// -/// Automatically map node relationships -/// -public class PlatformOptions : MasaConfigurationOptions -{ - public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; - - [JsonIgnore] - public virtual string? ParentSection { get; init; } = "AppId"; - - [JsonIgnore] - public virtual string? Section { get; init; } = "Platforms"; - - public string Name { get; set; } -} - -public class CustomDccSectionOptions -{ - /// - /// The environment name. - /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty - /// - public string? Environment { get; set; } = null; - - /// - /// The cluster name. - /// - public string? Cluster { get; set; } - - /// - /// The app id. - /// - public string AppId { get; set; } = default!; - - public List ConfigObjects { get; set; } = default!; - - public string? Secret { get; set; } -} -``` - -How to use configuration๏ผš - -```c# -var app = builder.Build(); - -app.MapGet("/GetPlatform", ([FromServices] IOptions option) => -{ - //recommend - return System.Text.Json.JsonSerializer.Serialize(option.Value);//Or use IOptionsMonitor to support monitoring changes -}); - -app.MapGet("/GetPlatformByMonitor", ([FromServices] IOptionsMonitor options) => -{ - options.OnChange(option => - { - //TODO Configuration update - }); - return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); -}); - -app.MapGet("/GetPlatformName", ([FromServices] IConfiguration configuration) => -{ - //Format ConfigurationAPI::: - return configuration["ConfigurationAPI::Platforms:Name"]; -}); - -app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, - [FromServices] IOptions configuration, - PlatformOptions newPlatform) => -{ - //Modify Dcc configuration - return configurationAPIManage.UpdateAsync(option.Value.Environment, - option.Value.Cluster, - option.Value.AppId, - "",newPlatform);//Here Replace-With-Your-ConfigObject is Platforms -}); -app.Run(); -``` - -How to update the configuration: - -```c# -var app = builder.Build(); - -app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, - [FromServices] IOptions configuration, - PlatformOptions newPlatform) => -{ - //Modify Dcc configuration - return configurationAPIManage.UpdateAsync(option.Value.Environment, - option.Value.Cluster, - option.Value.AppId, - "" - ,newPlatform); - //Here Replace-With-Your-ConfigObject is Platforms -}); - -app.Run(); -``` - -Summarize๏ผš - -Dcc provides remote configuration management and viewing capabilities for IConfiguration. For the complete capabilities of IConfiguration, please refer to the [document](../../Configuration/MASA.Contrib.Configuration/README.md) - -Platforms here is remote configuration, which introduces the effect and usage of remote configuration after mounting to IConfiguration. This configuration has nothing to do with Platforms in MASA.Contrib.Configuration. It just shows the use of the same configuration information in two sources. Ways and differences in mapping node relationships \ No newline at end of file diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md deleted file mode 100644 index d55b20ac9..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/README.zh-CN.md +++ /dev/null @@ -1,155 +0,0 @@ -ไธญ | [EN](README.md) - -## MASA.Contrib.BasicAbility.Dcc - -ไฝœ็”จ: - -้€š่ฟ‡Dccๆ‰ฉๅฑ•IConfiguration็ฎก็†่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ›ใ€‚ - -```c# -IConfiguration -โ”œโ”€โ”€ Local ๆœฌๅœฐ่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ -โ”œโ”€โ”€ ConfigurationAPI ่ฟœ็จ‹่Š‚็‚น๏ผˆๅ›บๅฎš Dccๆ‰ฉๅฑ•ๅ…ถ่ƒฝๅŠ›๏ผ‰ -โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰่Š‚็‚น -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary ๅญ—ๅ…ธ๏ผˆๅ›บๅฎš๏ผ‰DCCไธญ็ฑปๅž‹ไธบText็š„ๆŒ‚่ฝฝๅˆฐๆญคๅค„ -``` - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Configuration -Install-Package MASA.Contrib.BasicAbility.Dcc //ๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› -``` - -appsettings.json -``` -{ - //Dcc้…็ฝฎ๏ผŒๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ - "DccOptions": { - "ManageServiceAddress ": "http://localhost:8890", - "RedisOptions": { - "Servers": [ - { - "Host": "localhost", - "Port": 8889 - } - ], - "DefaultDatabase": 0, - "Password": "" - } - }, - "AppId": "Replace-With-Your-AppId", - "Environment": "Development", - "ConfigObjects": [ "Platforms" ], //ๅพ…ๆŒ‚่ฝฝ็š„ๅฏน่ฑกๅ, ๆญคๅค„ไผšๅฐ†Platforms้…็ฝฎๆŒ‚่ฝฝๅˆฐConfigurationAPI:่Š‚็‚นไธ‹ - "Secret": "", //Dcc App ็ง˜้’ฅ - "Cluster": "Default" -} - -``` - -```C# -builder.AddMasaConfiguration(configurationBuilder => -{ - configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDccๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› - - options.Mapping(SectionTypes.Local, "Appsettings", ""); //ๅฐ†CustomDccSectionOptionsๆ˜ ๅฐ„ๅˆฐLocalไธ‹็š„Appsettings่Š‚็‚น -}); - -/// -/// ่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป -/// -public class PlatformOptions : MasaConfigurationOptions -{ - public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; - - [JsonIgnore] - public virtual string? ParentSection { get; init; } = "Replace-With-Your-AppId"; - - [JsonIgnore] - public virtual string? Section { get; init; } = "Platforms"; - - public string Name { get; set; } -} - -public class CustomDccSectionOptions -{ - /// - /// The environment name. - /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty - /// - public string? Environment { get; set; } = null; - - /// - /// The cluster name. - /// - public string? Cluster { get; set; } - - /// - /// The app id. - /// - public string AppId { get; set; } = default!; - - public List ConfigObjects { get; set; } = default!; - - public string? Secret { get; set; } -} -``` - -ๅฆ‚ไฝ•ไฝฟ็”จ้…็ฝฎ๏ผš - -```c# -var app = builder.Build(); - -app.MapGet("/GetPlatform", ([FromServices] IOptions option) => -{ - //ๆŽจ่ - return System.Text.Json.JsonSerializer.Serialize(option.Value); -}); - -app.MapGet("/GetPlatformByMonitor", ([FromServices] IOptionsMonitor options) => -{ - options.OnChange(option => - { - //TODO ้…็ฝฎๆ›ดๆ–ฐ - }); - return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); -}); - -app.MapGet("/GetPlatformName", ([FromServices] IConfiguration configuration) => -{ - //ๆ ผๅผ๏ผšConfigurationAPI::: - return configuration["ConfigurationAPI::Platforms:Name"]; -}); - -app.Run(); -``` - -ๅฆ‚ไฝ•ๆ›ดๆ–ฐ้…็ฝฎ - - -```c# -var app = builder.Build(); - -app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, - [FromServices] IOptions configuration, - PlatformOptions newPlatform) => -{ - //ไฟฎๆ”นDcc้…็ฝฎ - return configurationAPIManage.UpdateAsync(option.Value.Environment, - option.Value.Cluster, - option.Value.AppId, - "" - ,newPlatform); - //ๆญคๅค„Replace-With-Your-ConfigObjectๆ˜ฏPlatforms -}); - -app.Run(); -``` - -ๆ€ป็ป“๏ผš - -DccไธบIConfigurationๆไพ›ไบ†่ฟœ็จ‹้…็ฝฎ็š„็ฎก็†ไปฅๅŠๆŸฅ็œ‹่ƒฝๅŠ›๏ผŒIConfigurationๅฎŒๆ•ด็š„่ƒฝๅŠ›่ฏทๆŸฅ็œ‹[ๆ–‡ๆกฃ](../../Configuration/MASA.Contrib.Configuration/README.zh-CN.md) - -ๆญคๅค„Platformsไธบ่ฟœ็จ‹้…็ฝฎ๏ผŒไป‹็ป็š„ๆ˜ฏ่ฟœ็จ‹้…็ฝฎๆŒ‚่ฝฝๅˆฐIConfigurationไน‹ๅŽ็š„ๆ•ˆๆžœไปฅๅŠ็”จๆณ•๏ผŒๆญค้…็ฝฎไธŽMASA.Contrib.ConfigurationไธญPlatforms็š„ๆฏซๆ— ๅ…ณ็ณป๏ผŒไป…ไป…ๆ˜ฏๅฑ•็คบๅŒไธ€ไธช้…็ฝฎไฟกๆฏๅœจไธคไธชๆบ็š„ไฝฟ็”จๆ–นๅผไปฅๅŠๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป็š„ๅทฎๅˆซ \ No newline at end of file diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs deleted file mode 100644 index a25cf5516..000000000 --- a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/_Imports.cs +++ /dev/null @@ -1,24 +0,0 @@ -global using MASA.BuildingBlocks.Configuration; -global using MASA.Contrib.BasicAbility.Dcc.Internal; -global using MASA.Contrib.BasicAbility.Dcc.Internal.Model; -global using MASA.Contrib.BasicAbility.Dcc.Internal.Parser; -global using MASA.Contrib.BasicAbility.Dcc.Options; -global using MASA.Utils.Caching.Core.DependencyInjection; -global using MASA.Utils.Caching.Core.Models; -global using MASA.Utils.Caching.DistributedMemory.DependencyInjection; -global using MASA.Utils.Caching.DistributedMemory.Interfaces; -global using MASA.Utils.Caching.Redis.DependencyInjection; -global using MASA.Utils.Caching.Redis.Extensions; -global using MASA.Utils.Caching.Redis.Models; -global using MASA.Utils.Caller.Core; -global using MASA.Utils.Caller.HttpClient; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using System.Collections.Concurrent; -global using System.Diagnostics; -global using System.Dynamic; -global using System.Text.Json; -global using static MASA.Contrib.BasicAbility.Dcc.Internal.Constants; - diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks deleted file mode 160000 index 0b0c99547..000000000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0b0c99547e4a5784813e6e6eb367ffbba518cd65 diff --git a/src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs b/src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs deleted file mode 100644 index 8bd2b27a0..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/LocalMasaConfigurationRepository.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace MASA.Contrib.Configuration; - -internal class LocalMasaConfigurationRepository : AbstractConfigurationRepository -{ - public override SectionTypes SectionType { get; init; } - - private ConcurrentDictionary _data = new(); - - public LocalMasaConfigurationRepository( - Dictionary sectionRelation, - ILoggerFactory? loggerFactory) - : base(loggerFactory) - { - this.SectionType = SectionTypes.Local; - foreach (var section in sectionRelation) - { - Initialize(section.Key, section.Value); - - ChangeToken.OnChange(() => section.Value.GetReloadToken(), () => - { - Initialize(section.Key, section.Value); - base.FireRepositoryChange(SectionType, Load()); - }); - } - } - - private void Initialize(string rootSectionName, IConfiguration configuration) - { - Dictionary data = new(); - GetData(rootSectionName, configuration, configuration.GetChildren(), ref data); - var properties = new Properties(data); - _data[rootSectionName] = properties; - } - - private void GetData(string rootSectionName, IConfiguration configuration, IEnumerable configurationSections, ref Dictionary dictionary) - { - foreach (var configurationSection in configurationSections) - { - var section = configuration.GetSection(configurationSection.Path); - - var childrenSections = section.GetChildren(); - - if (!section.Exists() || !childrenSections.Any()) - { - var key = string.IsNullOrEmpty(rootSectionName) ? section.Path : $"{rootSectionName}{ConfigurationPath.KeyDelimiter}{section.Path}"; - if (!dictionary.ContainsKey(key)) - { - dictionary.Add(key, configuration[section.Path]); - } - } - else - { - GetData(rootSectionName, configuration, childrenSections, ref dictionary); - } - } - } - - public override Properties Load() - { - Dictionary localProperties = new(); - foreach (var item in _data) - { - foreach (var key in item.Value.GetPropertyNames()) - { - localProperties[key] = item.Value.GetProperty(key) ?? string.Empty; - } - } - return new Properties(localProperties); - } -} - diff --git a/src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj b/src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj deleted file mode 100644 index d231705d6..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MASA.Contrib.Configuration.csproj +++ /dev/null @@ -1,17 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs deleted file mode 100644 index fd8294c4a..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationBuilder.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public class MasaConfigurationBuilder : IMasaConfigurationBuilder -{ - private readonly IConfigurationBuilder _builder; - - public IDictionary Properties => _builder.Properties; - - public IList Sources => _builder.Sources; - - public Dictionary GetSectionRelations() => SectionRelations; - - internal Dictionary SectionRelations { get; } = new(); - - internal List Repositories { get; } = new(); - - internal List Relations { get; } = new(); - - public MasaConfigurationBuilder(IConfigurationBuilder builder) - => _builder = builder; - - /// - /// - /// - /// - /// If section is null, it is mounted to the Local section - public void AddSection(IConfigurationBuilder configurationBuilder, string? sectionName = null) - { - if (configurationBuilder == null) - throw new ArgumentNullException(nameof(configurationBuilder)); - - if (configurationBuilder.Sources.Count == 0) - throw new ArgumentException("Source cannot be empty"); - - sectionName = sectionName ?? ""; - - if (SectionRelations.ContainsKey(sectionName)) - throw new ArgumentException("Section already exists", nameof(sectionName)); - - SectionRelations.Add(sectionName, configurationBuilder.Build()); - } - - public void AddRepository(IConfigurationRepository configurationRepository) - => Repositories.Add(configurationRepository); - - public void AddRelations(params ConfigurationRelationOptions[] relationOptions) - => Relations.AddRange(relationOptions); - - public IConfigurationBuilder Add(IConfigurationSource source) => _builder.Add(source); - - public IConfigurationRoot Build() => _builder.Build(); -} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs deleted file mode 100644 index 09552cf5c..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public static class MasaConfigurationExtensions -{ - public static void UseMasaOptions(this IMasaConfigurationBuilder builder, Action options) - { - var relation = new MasaRelationOptions(); - options.Invoke(relation); - builder.AddRelations(relation.Relations.ToArray()); - } - - internal static void AutoMapping(this MasaConfigurationBuilder builder, params Assembly[] assemblies) - { - var optionTypes = assemblies - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type != typeof(IMasaConfigurationOptions) && type != typeof(MasaConfigurationOptions) && typeof(IMasaConfigurationOptions).IsAssignableFrom(type)) - .ToList(); - optionTypes.ForEach(optionType => - { - var option = (IMasaConfigurationOptions)Activator.CreateInstance(optionType)!; - var sectionName = option.Section ?? optionType.Name; - if (builder.Relations.Any(relation => relation.SectionType == option.SectionType && relation.Section == sectionName)) - { - throw new ArgumentException("The section has been loaded, no need to load repeatedly, check whether there are duplicate sections or inheritance between auto-mapping classes"); - } - builder.AddRelations(new ConfigurationRelationOptions() - { - SectionType = option.SectionType, - ParentSection = option.ParentSection, - Section = sectionName, - ObjectType = optionType - }); - }); - } -} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs deleted file mode 100644 index 09e2fa6a7..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public abstract class MasaConfigurationOptions : IMasaConfigurationOptions -{ - /// - /// The name of the parent section, if it is empty, it will be mounted under SectionType, otherwise it will be mounted to the specified section under SectionType - /// - [JsonIgnore] - public virtual string? ParentSection { get; init; } = null; - - /// - /// The section null means same as the class name, else load from the specify section - /// - [JsonIgnore] - public virtual string? Section { get; init; } = null; - - [JsonIgnore] - public abstract SectionTypes SectionType { get; init; } -} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs deleted file mode 100644 index f4ba2b5cb..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationProvider.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public class MasaConfigurationProvider : ConfigurationProvider, IRepositoryChangeListener, IDisposable -{ - private readonly ConcurrentDictionary _data; - private readonly IEnumerable _configurationRepositories; - - public MasaConfigurationProvider(MasaConfigurationSource source) - { - _data = new(); - _configurationRepositories = source.Builder.Repositories; - - foreach (var configurationRepository in _configurationRepositories) - { - configurationRepository.AddChangeListener(this); - } - } - - public override void Load() - { - foreach (var configurationRepository in _configurationRepositories) - { - var properties = configurationRepository.Load(); - _data[configurationRepository.SectionType] = properties; - } - SetData(); - } - - public void OnRepositoryChange(SectionTypes sectionType, Properties newProperties) - { - if (_data[sectionType] == newProperties) - return; - - _data[sectionType] = newProperties; - - SetData(); - - OnReload(); - } - - void SetData() - { - Dictionary data = new(); - - foreach (var configurationType in _data.Keys) - { - var properties = _data[configurationType]; - foreach (var key in properties.GetPropertyNames()) - { - data[$"{configurationType}{ConfigurationPath.KeyDelimiter}{key}"] = properties.GetProperty(key)!; - } - } - - Data = data; - } - - public void Dispose() - { - foreach (var configurationRepository in _configurationRepositories) - { - configurationRepository.RemoveChangeListener(this); - } - } -} diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs b/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs deleted file mode 100644 index 7f57cf17e..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MasaConfigurationSource.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public class MasaConfigurationSource : IConfigurationSource -{ - internal MasaConfigurationBuilder Builder; - - public MasaConfigurationSource(MasaConfigurationBuilder builder) - { - Builder = builder; - } - - public IConfigurationProvider Build(IConfigurationBuilder builder) - { - return new MasaConfigurationProvider(this); - } -} - diff --git a/src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs b/src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs deleted file mode 100644 index b521c4efa..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/MasaRelationOptions.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public class MasaRelationOptions -{ - internal List Relations { get; } = new(); - - /// - /// Map Section relationship - /// - /// - /// - /// parent section, local section is the name of the locally configured section, and ConfigurationAPI is the name of the Appid where the configuration is located - /// The default is null, which is consistent with the mapping class name - /// - /// - public MasaRelationOptions Mapping(SectionTypes sectionType, string parentSection, string? section = null) - { - if (section == null) - section = typeof(TModel).Name; - - if (Relations.Any(relation => relation.SectionType == sectionType && relation.Section == section)) - throw new ArgumentOutOfRangeException(nameof(section), "The current section already has a configuration"); - - Relations.Add(new ConfigurationRelationOptions() - { - SectionType = sectionType, - ParentSection = parentSection, - Section = section, - ObjectType = typeof(TModel) - }); - return this; - } -} diff --git a/src/Configuration/MASA.Contrib.Configuration/README.md b/src/Configuration/MASA.Contrib.Configuration/README.md deleted file mode 100644 index 921dd0058..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/README.md +++ /dev/null @@ -1,135 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## MASA.Contrib.Configuration - -Structure: - -```c# -IConfiguration -โ”œโ”€โ”€ Local Local node (fixed) -โ”‚ โ”œโ”€โ”€ Appsettings Default local node -โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ Platforms Custom configuration -โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ โ”œโ”€โ”€ Name Parameter name -โ”œโ”€โ”€ ConfigurationAPI Remote node (fixed) -โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms Custom node -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name Parameter name -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary Dictionary (fixed) -``` - -Example๏ผš - -```C# -Install-Package MASA.Contrib.Configuration -Install-Package MASA.Contrib.BasicAbility.Dcc //DCC can provide remote configuration capabilities -```json -{ - //Custom configuration - "Platforms": { - "Name": "Masa.Demo" - }, - //Dcc configuration, extended Configuration capabilities, support remote configuration - "DccOptions": { - "ManageServiceAddress": "http://localhost:8890", - "RedisOptions": { - "Servers": [ - { - "Host": "localhost", - "Port": 8889 - } - ], - "DefaultDatabase": 0, - "Password": "" - } - }, - "AppId": "Replace-With-Your-AppId", - "ConfigObjects": [ "Platforms" ], //The name of the object to be mounted. Here, the Platforms configuration will be mounted under the ConfigurationAPI: node - "Secret": "", //Dcc App key - "Cluster": "Default" -} -``` - -Automatically map node relationships๏ผš - -```c# -public class PlatformOptions : MasaConfigurationOptions -{ - public override SectionTypes SectionType { get; init; } = SectionTypes.Local; - - [JsonIgnore] - public override string? ParentSection { get; init; } = "Appsettings"; - - [JsonIgnore] - public override string? Section { get; init; } = "Platforms"; - - public string Name { get; set; } -} - -//Use MasaConfiguration to take over Configuration, and mount the current Configuration to Local:Appsettings section by default -builder.AddMasaConfiguration(configurationBuilder => -{ - //configurationBuilder.UseDcc(builder.Services);//Use Dcc to extend Configuration capabilities and support remote configuration -}); -``` - -Or manually map node relationships๏ผš - -```C# -builder.AddMasaConfiguration(configurationBuilder => -{ - //configurationBuilder.UseDcc(builder.Services);//Use Dcc to extend Configuration capabilities and support remote configuration - - configurationBuilder.UseMasaOptions(options => - { - options.Mapping(SectionTypes.Local, "Appsettings", "Platforms"); //Map the PlatformOptions binding to the Local:Appsettings:Platforms node - }); -}); -``` - -how to use๏ผš - -```c# -var app = builder.Build(); - -app.Map("/GetPlatform", ([FromServices] IOptions option) => -{ - //Recommended (need to automatically or manually map the node relationship before it can be used) - return System.Text.Json.JsonSerializer.Serialize(option.Value); -}); - -app.Map("/GetPlatform", ([FromServices] IOptionsMonitor option) => -{ - //Recommended (need to automatically or manually map the node relationship before it can be used) - options.OnChange(option => - { - //TODO Configuration update service - }); - - return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); -}); - -app.Map("/GetPlatformName", ([FromServices] IConfiguration configuration) => -{ - //Base - return configuration["Local:Appsettings:Platforms:Name"]; -}); - -app.Run(); -``` - -How to take over more local nodes๏ผŸ - -```c# -builder.AddMasaConfiguration(builder =>{ - - builder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("custom.json", true, true), "Custom");//Mount the custom.json configuration under the Local:Custom node -}); -``` - -Tip๏ผš - -Configuration automatically obtains classes that inherit the IMasaConfigurationOptions interface by default, and maps the node relationship to facilitate obtaining configuration information through IOptions, IOptionsSnapshot, and IOptionsMonitor - -The above Platforms is a local configuration, used to demonstrate the effect and usage of the local configuration after being mounted to IConfiguration \ No newline at end of file diff --git a/src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md b/src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md deleted file mode 100644 index 0bd1a1830..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/README.zh-CN.md +++ /dev/null @@ -1,140 +0,0 @@ -ไธญ | [EN](README.md) - -## MASA.Contrib.Configuration - -็ป“ๆž„: - -```c# -IConfiguration -โ”œโ”€โ”€ Local ๆœฌๅœฐ่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ -โ”‚ โ”œโ”€โ”€ Appsettings ้ป˜่ฎคๆœฌๅœฐ่Š‚็‚น -โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰้…็ฝฎ -โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ -โ”œโ”€โ”€ ConfigurationAPI ่ฟœ็จ‹่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ -โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰่Š‚็‚น -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ -โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary ๅญ—ๅ…ธ๏ผˆๅ›บๅฎš๏ผ‰ -``` - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Configuration -Install-Package MASA.Contrib.BasicAbility.Dcc //DCCๅฏๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› -``` - -appsettings.json -```json -{ - //่‡ชๅฎšไน‰้…็ฝฎ - "Platforms": { - "Name": "Masa.Demo" - }, - //Dcc้…็ฝฎ๏ผŒๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ - "DccOptions": { - "ManageServiceAddress ": "http://localhost:8890", - "RedisOptions": { - "Servers": [ - { - "Host": "localhost", - "Port": 8889 - } - ], - "DefaultDatabase": 0, - "Password": "" - } - }, - "AppId": "Replace-With-Your-AppId", - "ConfigObjects": [ "Platforms" ], // ่ฆๆŒ‚่ฝฝ็š„ๅฏน่ฑกๅ็งฐ,ๆญคๅค„ไผšๅฐ†Platforms้…็ฝฎๆŒ‚่ฝฝๅˆฐConfigurationAPI:่Š‚็‚นไธ‹ - "Secret": "", //Dcc App ็ง˜้’ฅ - "Cluster": "Default" -} -``` - -่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผš - -```c# -/// -/// ่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป -/// -public class PlatformOptions : MasaConfigurationOptions -{ - public override SectionTypes SectionType { get; init; } = SectionTypes.Local; - - [JsonIgnore] - public override string? ParentSection { get; init; } = "Appsettings"; - - [JsonIgnore] - public override string? Section { get; init; } = "Platforms"; - - public string Name { get; set; } -} - -//ไฝฟ็”จMasaConfigurationๆŽฅ็ฎกConfiguration๏ผŒ้ป˜่ฎคไผšๅฐ†ๅฝ“ๅ‰็š„ConfigurationๆŒ‚่ฝฝๅˆฐLocal:Appsettings่Š‚็‚น -builder.AddMasaConfiguration(configurationBuilder => -{ - //configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDcc ๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ -}); -``` - -ๆˆ–ๆ‰‹ๅŠจๆทปๅŠ ๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผš - -```C# -builder.AddMasaConfiguration(configurationBuilder => -{ - //configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDcc ๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ - - configurationBuilder.UseMasaOptions(options => - { - options.Mapping(SectionTypes.Local, "Appsettings", "Platforms"); //ๅฐ†PlatformOptions็ป‘ๅฎšๆ˜ ๅฐ„ๅˆฐLocal:Appsettings:Platforms่Š‚็‚น - }); -}); -``` - -ๅฆ‚ไฝ•ไฝฟ็”จ๏ผš - -```c# -var app = builder.Build(); - -app.Map("/GetPlatform", ([FromServices] IOptions option) => -{ - //ๆŽจ่๏ผˆ้œ€่ฆ่‡ชๅŠจๆˆ–ๆ‰‹ๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณปๅŽๆ‰่ƒฝไฝฟ็”จ๏ผ‰ - return System.Text.Json.JsonSerializer.Serialize(option.Value); -}); - -app.Map("/GetPlatform", ([FromServices] IOptionsMonitor option) => -{ - options.OnChange(option => - { - //TODO ้…็ฝฎๆ›ดๆ–ฐไธšๅŠก - }); - - return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); -});//ๆŽจ่๏ผˆ้œ€่ฆ่‡ชๅŠจๆˆ–ๆ‰‹ๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณปๅŽๆ‰่ƒฝไฝฟ็”จ๏ผ‰ - -app.Map("/GetPlatformName", ([FromServices] IConfiguration configuration) => -{ - //ๅŸบ็ก€ - return configuration["Local:Appsettings:Platforms:Name"]; -}); - -app.Run(); -``` - -ๅฆ‚ไฝ•ๆŽฅ็ฎกๆ›ดๅคš็š„ๆœฌๅœฐ่Š‚็‚น๏ผŸ - -```c# -builder.AddMasaConfiguration(builder =>{ - - builder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("custom.json", true, true), "Custom");//ๅฐ†custom.json้…็ฝฎๆŒ‚่ฝฝๅˆฐLocal:Custom่Š‚็‚นไธ‹ -}); -``` - -ๆ็คบ๏ผš - -Configuration้ป˜่ฎค่‡ชๅŠจ่Žทๅ–็ปงๆ‰ฟIMasaConfigurationOptionsๆŽฅๅฃ็š„็ฑป๏ผŒๅนถๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผŒๆ–นไพฟ้€š่ฟ‡IOptionsใ€IOptionsSnapshotใ€IOptionsMonitor่Žทๅ–้…็ฝฎไฟกๆฏ - -ไธŠๆ–‡Platformsไธบๆœฌๅœฐ้…็ฝฎ๏ผŒ็”จไบŽๆผ”็คบๆœฌๅœฐ้…็ฝฎๆŒ‚่ฝฝๅˆฐIConfigurationๅŽ็š„ๆ•ˆๆžœไปฅๅŠไฝฟ็”จ็”จๆณ• \ No newline at end of file diff --git a/src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs b/src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs deleted file mode 100644 index b06ea09b4..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,138 +0,0 @@ -namespace MASA.Contrib.Configuration; - -public static class ServiceCollectionExtensions -{ - public static WebApplicationBuilder AddMasaConfiguration( - this WebApplicationBuilder builder, - Action? configureDelegate = null) - => builder.AddMasaConfiguration(configureDelegate, - "Appsettings", - AppDomain.CurrentDomain.GetAssemblies()); - - public static WebApplicationBuilder AddMasaConfiguration( - this WebApplicationBuilder builder, - Action? configureDelegate, - string defaultSectionName = "Appsettings", - params Assembly[] assemblies) - { - var configurationBuilder = GetConfigurationBuilder(builder.Configuration); - - IConfigurationRoot masaConfiguration = builder.Services.CreateMasaConfiguration(configureDelegate, configurationBuilder, defaultSectionName, assemblies); - if (!masaConfiguration.Providers.Any()) - return builder; - - Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.ConfigureAppConfiguration(builder.Host, configBuilder => - { - configBuilder.Sources.Clear(); - }); - builder.Configuration.AddConfiguration(masaConfiguration); - - return builder; - } - - public static IConfigurationRoot CreateMasaConfiguration( - this IServiceCollection services, - Action? configureDelegate, - IConfigurationBuilder? configurationBuilder = null, - string defaultSectionName = "Appsettings", - params Assembly[] assemblies) - { - if (services.Any(service => service.ImplementationType == typeof(MasaConfigurationProvider))) - return new ConfigurationBuilder().Build(); - - services.AddSingleton(); - services.AddOptions(); - - MasaConfigurationBuilder masaConfigurationBuilder = new MasaConfigurationBuilder(new ConfigurationBuilder()); - if (configurationBuilder != null) - { - masaConfigurationBuilder.AddSection(configurationBuilder, defaultSectionName); - } - configureDelegate?.Invoke(masaConfigurationBuilder); - - if (masaConfigurationBuilder.SectionRelations.Count == 0) - throw new Exception("Please add the section to be loaded"); - - var localConfigurationRepository = new LocalMasaConfigurationRepository(masaConfigurationBuilder.SectionRelations, services.BuildServiceProvider().GetService()); - masaConfigurationBuilder.AddRepository(localConfigurationRepository); - - var source = new MasaConfigurationSource(masaConfigurationBuilder); - var configuration = masaConfigurationBuilder.Add(source).Build(); - - masaConfigurationBuilder.AutoMapping(assemblies); - masaConfigurationBuilder.Relations.ForEach(relation => - { - List sectionNames = new List() - { - relation.SectionType.ToString(), - }; - if (!string.IsNullOrEmpty(relation.ParentSection)) - sectionNames.Add(relation.ParentSection); - - if (relation.Section != "") - { - sectionNames.AddRange(relation.Section.Split(ConfigurationPath.KeyDelimiter)); - } - - services.ConfigureOption(configuration, sectionNames, relation.ObjectType); - }); - - return configuration; - } - - private static IConfigurationBuilder GetConfigurationBuilder(ConfigurationManager configuration) - { - var configurationBuilder = new ConfigurationBuilder(); - foreach (var source in ((IConfigurationBuilder)configuration).Sources) - { - configurationBuilder.Add(source); - } - return configurationBuilder; - } - - private static void ClearSource(this WebApplicationBuilder builder) - { - Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.ConfigureAppConfiguration(builder.Host, configBuilder => - { - configBuilder.Sources.Clear(); - }); - } - - internal static void ConfigureOption( - this IServiceCollection services, - IConfiguration configuration, - List sectionNames, Type optionType) - { - IConfigurationSection? configurationSection = null; - foreach (var sectionName in sectionNames) - { - if (configurationSection == null) - configurationSection = configuration.GetSection(sectionName); - else - configurationSection = configurationSection.GetSection(sectionName); - } - if (!configurationSection.Exists()) - { - throw new ArgumentNullException("Section", "Check if the mapping section is correct"); - } - - var configurationChangeTokenSource = - Activator.CreateInstance(typeof(ConfigurationChangeTokenSource<>).MakeGenericType(optionType), string.Empty, - configurationSection)!; - services.TryAdd(new ServiceDescriptor(typeof(IOptionsChangeTokenSource<>).MakeGenericType(optionType), - configurationChangeTokenSource)); - - Action configureBinder = _ => { }; - var configureOptions = - Activator.CreateInstance(typeof(NamedConfigureFromConfigurationOptions<>).MakeGenericType(optionType), - string.Empty, - configurationSection, configureBinder)!; - services.TryAdd(new ServiceDescriptor(typeof(IConfigureOptions<>).MakeGenericType(optionType), - configureOptions)); - } - - private class MasaConfigurationProvider - { - - } -} diff --git a/src/Configuration/MASA.Contrib.Configuration/_Imports.cs b/src/Configuration/MASA.Contrib.Configuration/_Imports.cs deleted file mode 100644 index 85d4ce738..000000000 --- a/src/Configuration/MASA.Contrib.Configuration/_Imports.cs +++ /dev/null @@ -1,15 +0,0 @@ -global using MASA.BuildingBlocks.Configuration; -global using MASA.BuildingBlocks.Configuration.Options; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using Microsoft.Extensions.Primitives; -global using System; -global using System.Collections.Concurrent; -global using System.Collections.Generic; -global using System.Linq; -global using System.Reflection; -global using System.Text.Json.Serialization; diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs deleted file mode 100644 index ee5ae368f..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/DispatcherOptionsExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF; - -public static class DispatcherOptionsExtensions -{ - public static IDispatcherOptions UseRepository( - this IDispatcherOptions options) - where TDbContext : DbContext - => options.UseRepository(AppDomain.CurrentDomain.GetAssemblies()); - - public static IDispatcherOptions UseRepository( - this IDispatcherOptions options, - params Assembly[] assemblies) - where TDbContext : DbContext - { - if (options.Services == null) - throw new ArgumentNullException(nameof(options.Services)); - - if (options.Services.Any(service => service.ImplementationType == typeof(RepositoryProvider))) - return options; - - options.Services.AddSingleton(); - - if (options.Services.All(service => service.ServiceType != typeof(IUnitOfWork))) - throw new Exception("Please add UoW first."); - - options.Services.TryAddRepository(assemblies); - return options; - } - - private class RepositoryProvider - { - - } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs deleted file mode 100644 index b47f613b9..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/LinqExtensions.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Internal; - -internal static class LinqExtensions -{ - public static IQueryable GetQueryable(this IQueryable query, Dictionary fields) where TEntity : class - { - foreach (var field in fields) - { - query = query.GetQueryable(field.Key, field.Value); - } - return query; - } - - private static IQueryable GetQueryable(this IQueryable query, string field, object val) where TEntity : class - { - Type type = typeof(TEntity); - var parameter = Expression.Parameter(type, "entity"); - - PropertyInfo property = type.GetProperty(field)!; - Expression expProperty = Expression.Property(parameter, property.Name); - - Expression> valueLamda = () => val; - Expression expValue = Expression.Convert(valueLamda.Body, property.PropertyType); - Expression expression = Expression.Equal(expProperty, expValue); - Expression> filter = (Expression>)Expression.Lambda(expression, parameter); - return query.Where(filter); - } - - public static IQueryable OrderBy(this IQueryable query, Dictionary fields) where TEntity : class - { - var index = 0; - foreach (var field in fields) - { - if (index == 0) - query = query.OrderBy(field.Key, field.Value); - else - query = query.ThenBy(field.Key, field.Value); - index++; - } - - return query; - } - - private static IQueryable OrderBy(this IQueryable query, string field, bool desc) where TEntity : class - { - ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity)); - Expression key = Expression.Property(parameterExpression, field); - var propertyInfo = GetPropertyInfo(typeof(TEntity), field); - var orderExpression = GetOrderExpression(typeof(TEntity), propertyInfo); - if (desc) - { - var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2); - var genericMethod = method!.MakeGenericMethod(typeof(TEntity), propertyInfo.PropertyType); - return (genericMethod.Invoke(null, new object[] { query, orderExpression }) as IQueryable)!; - } - else - { - var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2); - var genericMethod = method!.MakeGenericMethod(typeof(TEntity), propertyInfo.PropertyType); - return (IQueryable)genericMethod.Invoke(null, new object[] { query, orderExpression })!; - } - } - - private static IQueryable ThenBy(this IQueryable query, string field, bool desc) where T : class - { - ParameterExpression parameterExpression = Expression.Parameter(typeof(T)); - Expression key = Expression.Property(parameterExpression, field); - var propertyInfo = GetPropertyInfo(typeof(T), field); - var orderExpression = GetOrderExpression(typeof(T), propertyInfo); - if (desc) - { - var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "ThenByDescending" && m.GetParameters().Length == 2); - var genericMethod = method!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType); - return (genericMethod.Invoke(null, new object[] { query, orderExpression }) as IQueryable)!; - } - else - { - var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "ThenBy" && m.GetParameters().Length == 2); - var genericMethod = method!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType); - return (IQueryable)genericMethod.Invoke(null, new object[] { query, orderExpression })!; - } - } - - private static PropertyInfo GetPropertyInfo(Type entityType, string field) - => entityType.GetProperties().FirstOrDefault(p => p.Name.Equals(field, StringComparison.OrdinalIgnoreCase))!; - - private static LambdaExpression GetOrderExpression(Type entityType, PropertyInfo propertyInfo) - { - var parametersExpression = Expression.Parameter(entityType); - var fieldExpression = Expression.PropertyOrField(parametersExpression, propertyInfo.Name); - return Expression.Lambda(fieldExpression, parametersExpression); - } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs deleted file mode 100644 index f009c3c8f..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Internal; - -internal static class ServiceCollectionRepositoryExtensions -{ - /// - /// The relationship between entity and keys - /// - public static Dictionary Relations = new(); - - public static IServiceCollection TryAddRepository( - this IServiceCollection services, - params Assembly[] assemblies) - where TDbContext : DbContext - { - if (assemblies == null || assemblies.Length == 0) - { - throw new ArgumentNullException(nameof(assemblies)); - } - - var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()); - var entityTypes = allTypes.Where(type => type.IsAggregateRootEntity()); - foreach (var entityType in entityTypes) - { - var repositoryInterfaceType = typeof(IRepository<>).MakeGenericType(entityType); - - services.TryAddAddDefaultRepository(repositoryInterfaceType, GetRepositoryImplementationType(typeof(TDbContext), entityType)); - - services.TryAddCustomRepository(repositoryInterfaceType, allTypes.ToArray()); - - var keys = GetKeys(entityType); - CheckKeys(entityType, keys); - Relations.TryAdd(entityType, keys); - } - - return services; - } - - private static string[] GetKeys(Type entityType) - { - IAggregateRoot aggregateRoot; - try - { - var constructorInfo = entityType - .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .FirstOrDefault(con => !con.CustomAttributes.Any()); - - if (constructorInfo == null) - throw new ArgumentNullException("The entity needs to have an empty constructor"); - - aggregateRoot = (IAggregateRoot)Activator.CreateInstance(entityType, constructorInfo.IsPrivate)!; - } - catch (Exception) - { - throw new ArgumentNullException("The entity needs to have an empty constructor"); - } - - var keys = aggregateRoot.GetKeys().Select(k => k.Name).ToArray(); - if (keys.Length != keys.Where(key => !string.IsNullOrEmpty(key)).Distinct().Count()) - throw new ArgumentException("The joint primary key cannot be empty"); - - return keys; - } - - /// - /// Check if the combined primary key is correct - /// - private static void CheckKeys(Type entityType, string[] fields) - { - foreach (var field in fields) - { - if (!entityType.GetProperties().Any(p => p.Name.Equals(field, StringComparison.OrdinalIgnoreCase))!) - throw new ArgumentException("Check if the combined primary key is correct"); - } - } - - private static bool IsAggregateRootEntity(this Type type) - => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && type != typeof(Entity) && typeof(IAggregateRoot).IsAssignableFrom(type); - - private static IServiceCollection TryAddCustomRepository(this IServiceCollection services, Type repositoryInterfaceType, Type[] allTypes) - { - var customerRepositoryInterfaceTypes = allTypes.Where(type => type.GetInterfaces().Any(t => t == repositoryInterfaceType) && type.IsInterface && !type.IsGenericType); - foreach (var customerRepositoryInterfaceType in customerRepositoryInterfaceTypes) - { - var customerRepositoryImplementationTypes = allTypes.Where(type => type.IsClass && customerRepositoryInterfaceType.IsAssignableFrom(type)).ToList(); - if (customerRepositoryImplementationTypes.Count != 1) - { - throw new NotSupportedException($"The number of types of {customerRepositoryInterfaceType.FullName} implementation classes must be 1"); - } - services.TryAddScoped(customerRepositoryInterfaceType, customerRepositoryImplementationTypes.FirstOrDefault()!); - } - return services; - } - - private static IServiceCollection TryAddAddDefaultRepository(this IServiceCollection services, Type repositoryInterfaceType, Type repositoryImplementationType) - { - if (repositoryInterfaceType.IsAssignableFrom(repositoryImplementationType)) - { - services.TryAddScoped(repositoryInterfaceType, repositoryImplementationType); - } - return services; - } - - public static Type GetRepositoryImplementationType(Type dbContextType, Type entityType) - => typeof(Repository<,>).MakeGenericType(dbContextType, entityType); -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj deleted file mode 100644 index 679608d24..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/MASA.Contrib.DDD.Domain.Repository.EF.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - - - - - diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md deleted file mode 100644 index 54dc8f883..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.md +++ /dev/null @@ -1,71 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## MASA.Contrib.DDD.Domain.Repository.EF - -Example๏ผš - -```c# -Install-Package MASA.Contrib.DDD.Domain.Repository.EF -``` - -> Advantages: The EF version of IRepository provides basic CRUD - -1. Add Repository.EF - -```c# -builder.Services -.AddDomainEventBus(options => -{ - options.UseRepository();//Use the EF version of Repository to achieve -} -``` - -2. How to use - -```C# -public class ProductItem -{ - public string Name { get; set; } -} - -public class DemoService : ServiceBase -{ - public CatalogService(IServiceCollection services) : base(services) - { - - } - - public async Task CreateProduct(ProductItem product,[FromService]IRepository repository) - { - await repository.AddAsync(product); - await repository.UnitOfWork.SaveChangesAsync(); - } -} -``` - -If the method defined by IRepository is not enough to support the business, you can customize the Repository - -```C# -public interface IProductRepository : IRepository -{ - Task> ItemsWithNameAsync(string name); -} - -public class ProductRepository : Repository, IProductRepository -{ - public Task> ItemsWithNameAsync(string name) - { - //Todo - } -} -``` - -In use๏ผš - -```C# -public async Task> ItemsWithNameAsync(string name, [FromService] IProductRepository productRepository) -{ - return await productRepository.ItemsWithNameAsync(name); -} -``` - diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md deleted file mode 100644 index 3a9cf09c5..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/README.zh-CN.md +++ /dev/null @@ -1,71 +0,0 @@ -ไธญ | [EN](README.md) - -## MASA.Contrib.DDD.Domain.Repository.EF - -็”จไพ‹๏ผš - -```c# -Install-Package MASA.Contrib.DDD.Domain.Repository.EF -``` - -> ไผ˜ๅŠฟ๏ผšIRepository็š„EF็‰ˆๅฎž็Žฐ๏ผŒๆไพ›ไบ†ๅŸบ็ก€็š„CRUD - -1. ๆทปๅŠ Repository.EF - -```c# -builder.Services -.AddDomainEventBus(options => -{ - options.UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ -} -``` - -2. ๅฆ‚ไฝ•ไฝฟ็”จ - -```C# -public class ProductItem -{ - public string Name { get; set; } -} - -public class DemoService : ServiceBase -{ - public CatalogService(IServiceCollection services) : base(services) - { - - } - - public async Task CreateProduct(ProductItem product,[FromService]IRepository repository) - { - await repository.AddAsync(product); - await repository.UnitOfWork.SaveChangesAsync(); - } -} -``` - -ๅฆ‚ๆžœIRepositoryๅฎšไน‰็š„ๆ–นๆณ•ไธ่ถณไปฅๆ”ฏๆ’‘ไธšๅŠก๏ผŒๅˆ™ๅฏไปฅ่‡ชๅฎšไน‰Repository - -```C# -public interface IProductRepository : IRepository -{ - Task> ItemsWithNameAsync(string name); -} - -public class ProductRepository : Repository, IProductRepository -{ - public Task> ItemsWithNameAsync(string name) - { - //Todo - } -} -``` - -ๅœจไฝฟ็”จไธŠ๏ผš - -```C# -public async Task> ItemsWithNameAsync(string name, [FromService] IProductRepository productRepository) -{ - return await productRepository.ItemsWithNameAsync(name); -} -``` - diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs deleted file mode 100644 index bf1894894..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/Repository.cs +++ /dev/null @@ -1,213 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF; - -public class Repository : BaseRepository - where TEntity : class, IAggregateRoot - where TDbContext : DbContext -{ - protected readonly TDbContext _context; - - public Repository(TDbContext context, IUnitOfWork unitOfWork) - { - _context = context; - UnitOfWork = unitOfWork; - } - - public override bool TransactionHasBegun - => _context.Database.CurrentTransaction != null; - - public override DbTransaction Transaction - { - get - { - if (!UseTransaction) - throw new NotSupportedException(nameof(Transaction)); - - if (TransactionHasBegun) - return _context.Database.CurrentTransaction!.GetDbTransaction(); - - return _context.Database.BeginTransaction().GetDbTransaction(); - } - } - - public override bool UseTransaction - { - get => UnitOfWork.UseTransaction; - set => UnitOfWork.UseTransaction = value; - } - - public override IUnitOfWork UnitOfWork { get; } - - public override EntityState EntityState - { - get => UnitOfWork.EntityState; - set - { - UnitOfWork.EntityState = value; - if (value == EntityState.Changed) - CheckAndOpenTransaction(); - } - } - - public override async ValueTask AddAsync( - TEntity entity, - CancellationToken cancellationToken = default) - { - var response = (await _context.AddAsync(entity, cancellationToken).AsTask()).Entity; - EntityState = EntityState.Changed; - return response; - } - - public override async Task AddRangeAsync( - IEnumerable entities, - CancellationToken cancellationToken = default) - { - await _context.AddRangeAsync(entities, cancellationToken); - EntityState = EntityState.Changed; - } - - public override Task CommitAsync(CancellationToken cancellationToken = default) - => UnitOfWork.CommitAsync(cancellationToken); - - public override async ValueTask DisposeAsync() => await _context.DisposeAsync(); - - public override void Dispose() => _context.Dispose(); - - public override Task FindAsync( - object?[]? keyValues, - CancellationToken cancellationToken = default) - { - if (keyValues == null) - return Task.FromResult(default(TEntity?)); - - var keys = GetKeys(typeof(TEntity)); - Dictionary fields = new(); - for (var i = 0; i < keys.Length; i++) - { - fields.Add(keys[i], keyValues[i]!); - } - - return _context.Set().IgnoreQueryFilters().GetQueryable(fields).FirstOrDefaultAsync(cancellationToken); - } - - public override Task FindAsync( - Expression> predicate, - CancellationToken cancellationToken = default) - => _context.Set().Where(predicate).FirstOrDefaultAsync(cancellationToken); - - public override async Task GetCountAsync(CancellationToken cancellationToken = default) - => await _context.Set().LongCountAsync(cancellationToken); - - public override Task GetCountAsync( - Expression> predicate, - CancellationToken cancellationToken = default) - => _context.Set().LongCountAsync(predicate, cancellationToken); - - public override async Task> GetListAsync(CancellationToken cancellationToken = default) - => await _context.Set().ToListAsync(cancellationToken); - - public override async Task> GetListAsync( - Expression> predicate, - CancellationToken cancellationToken = default) - => await _context.Set().Where(predicate).ToListAsync(cancellationToken); - - /// - /// - /// - /// - /// - /// asc or desc, default asc - /// - /// - public override Task> GetPaginatedListAsync( - int skip, - int take, - Dictionary? sorting, - CancellationToken cancellationToken = default) - { - sorting ??= new Dictionary(GetKeys(typeof(TEntity)).Select(key => new KeyValuePair(key, false))); - - return _context.Set().OrderBy(sorting).Skip(skip).Take(take).ToListAsync(cancellationToken); - } - - /// - /// - /// - /// condition - /// - /// - /// asc or desc, default asc - /// - /// - public override Task> GetPaginatedListAsync( - Expression> predicate, - int skip, - int take, - Dictionary? sorting, - CancellationToken cancellationToken = default) - { - sorting ??= new Dictionary(GetKeys(typeof(TEntity)).Select(key => new KeyValuePair(key, false))); - - return _context.Set().Where(predicate).OrderBy(sorting).Skip(skip).Take(take).ToListAsync(cancellationToken); - } - - public override Task RemoveAsync(TEntity entity, CancellationToken cancellationToken = default) - { - _context.Set().Remove(entity); - EntityState = EntityState.Changed; - return Task.FromResult(entity); - } - - public override async Task RemoveAsync(Expression> predicate, CancellationToken cancellationToken = default) - { - var entities = await GetListAsync(predicate, cancellationToken); - EntityState = EntityState.Changed; - _context.Set().RemoveRange(entities); - } - - public override Task RemoveRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) - { - _context.Set().RemoveRange(entities); - EntityState = EntityState.Changed; - return Task.CompletedTask; - } - - public override Task RollbackAsync(CancellationToken cancellationToken = default) - => UnitOfWork.RollbackAsync(cancellationToken); - - public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - await UnitOfWork.SaveChangesAsync(cancellationToken); - } - - public override Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) - { - _context.Set().Update(entity); - EntityState = EntityState.Changed; - return Task.FromResult(entity); - } - - public override Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) - { - _context.Set().UpdateRange(entities); - EntityState = EntityState.Changed; - return Task.CompletedTask; - } - - /// - /// When additions, deletions and changes are made through the Repository and the transaction is currently allowed and the transaction is not opened, the transaction is started - /// - private void CheckAndOpenTransaction() - { - if (!UnitOfWork.UseTransaction) - return; - - if (!UnitOfWork.TransactionHasBegun) - { - _ = UnitOfWork.Transaction; // Open the transaction - } - CommitState = CommitState.UnCommited; - } - - protected string[] GetKeys(Type entityType) - => ServiceCollectionRepositoryExtensions.Relations[entityType]!; -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs b/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs deleted file mode 100644 index 95dee48cd..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain.Repository.EF/_Imports.cs +++ /dev/null @@ -1,15 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.DDD.Domain.Entities; -global using MASA.BuildingBlocks.DDD.Domain.Repositories; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.DDD.Domain.Repository.EF.Internal; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.Storage; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using System.Collections.Concurrent; -global using System.Data.Common; -global using System.Linq.Expressions; -global using System.Reflection; -global using EntityState = MASA.BuildingBlocks.Data.UoW.EntityState; diff --git a/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs b/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs deleted file mode 100644 index 67fc8ce04..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/DomainEventBus.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace MASA.Contrib.DDD.Domain; - -public class DomainEventBus : IDomainEventBus -{ - protected readonly IEventBus _eventBus; - protected readonly IIntegrationEventBus _integrationEventBus; - private readonly IUnitOfWork _unitOfWork; - private readonly DispatcherOptions _options; - - private readonly ConcurrentQueue> _eventQueue = new(); - - public DomainEventBus( - IEventBus eventBus, - IIntegrationEventBus integrationEventBus, - IUnitOfWork unitOfWork, - IOptions options) - { - _eventBus = eventBus; - _integrationEventBus = integrationEventBus; - _unitOfWork = unitOfWork; - _options = options.Value; - } - - public async Task PublishAsync(TEvent @event) where TEvent : IEvent - { - if (@event is IDomainEvent domainEvent && !IsAssignableFromDomainQuery(@event.GetType())) - { - domainEvent.UnitOfWork = _unitOfWork; - } - if (@event is IIntegrationEvent integrationEvent) - { - await _integrationEventBus.PublishAsync((TEvent)integrationEvent); - } - else - { - await _eventBus.PublishAsync(@event); - } - - bool IsAssignableFromDomainQuery(Type? type) - { - if (type == null) - return false; - - if (!type.IsGenericType) - { - return IsAssignableFromDomainQuery(type.BaseType); - } - return type.GetInterfaces().Any(type => type.GetGenericTypeDefinition() == typeof(IDomainQuery<>)); - } - } - - public Task Enqueue(TDomentEvent @event) where TDomentEvent : IDomainEvent - { - _eventQueue.Enqueue(new KeyValuePair(@event.GetType(), @event)); - return Task.CompletedTask; - } - - public async Task PublishQueueAsync() - { - while (_eventQueue.TryDequeue(out KeyValuePair @event)) - { - await PublishAsync(@event.Key, @event.Value); - } - } - - private async Task PublishAsync(Type type, TEvent @event) where TEvent : IEvent - { - if (@event is IIntegrationEvent integrationEvent) - { - await PublishAsync(integrationEvent); - } - else - { - var parameters = Convert.ChangeType(@event, type); - var invokeDelegate = InvokeBuilder.Build(_eventBus.GetType(), nameof(_eventBus.PublishAsync), type); - await invokeDelegate.Invoke(_eventBus, parameters); - } - } - - public async Task CommitAsync(CancellationToken cancellationToken = default) - => await _unitOfWork.CommitAsync(cancellationToken); - - public IEnumerable GetAllEventTypes() => _options.AllEventTypes.Concat(_eventBus.GetAllEventTypes()).Distinct(); -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/DomainService.cs b/src/DDD/MASA.Contrib.DDD.Domain/DomainService.cs deleted file mode 100644 index 5e21703e7..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/DomainService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain; - -public class DomainService : IDomainService -{ - public IDomainEventBus EventBus { get; } - - public DomainService(IDomainEventBus eventBus) => EventBus = eventBus; -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs deleted file mode 100644 index 24c351c5d..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainCommand.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Events; - -public record DomainCommand(Guid Id, DateTime CreationTime) : IDomainCommand -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - [JsonIgnore] - public IUnitOfWork? UnitOfWork { get; set; } - - public DomainCommand() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs deleted file mode 100644 index 2aeb5a3fc..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Events; - -public record DomainEvent(Guid Id, DateTime CreationTime) : IDomainEvent -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - [JsonIgnore] - public IUnitOfWork? UnitOfWork { get; set; } - - public DomainEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } - -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs deleted file mode 100644 index e72db94d6..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/DomainQuery.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Events; - -public abstract record DomainQuery(Guid Id, DateTime CreationTime) : IDomainQuery - where TResult : notnull -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - [JsonIgnore] - public IUnitOfWork? UnitOfWork - { - get => null; - set => throw new NotSupportedException(nameof(UnitOfWork)); - } - - public abstract TResult Result { get; set; } - - public DomainQuery() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs b/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs deleted file mode 100644 index 3c027cb0f..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/Events/IntegrationDomainEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Events; - -public abstract record IntegrationDomainEvent(Guid Id, DateTime CreationTime) : DomainEvent(Id, CreationTime), IIntegrationDomainEvent -{ - [JsonIgnore] - public abstract string Topic { get; set; } - - public IntegrationDomainEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs b/src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs deleted file mode 100644 index 74c3f9690..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/Internal/InvokeBuilder.cs +++ /dev/null @@ -1,21 +0,0 @@ -๏ปฟnamespace MASA.Contrib.DDD.Domain.Internal; - -internal class InvokeBuilder -{ - internal delegate Task TaskInvokeDelegate(object target, params object[] parameters); - - internal static TaskInvokeDelegate Build(Type targetType, string methodName, Type parameterType) - { - var targetParameter = Expression.Parameter(typeof(object), "target"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(0)); - var valueCast = Expression.Convert(valueObj, parameterType); - var instanceCast = Expression.Convert(targetParameter, targetType); - var methodCall = Expression.Call(instanceCast, methodName, new[] { parameterType }, valueCast); - - var castMethodCall = Expression.Convert(methodCall, typeof(Task)); - var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); - return lambda.Compile(); - } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj b/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj deleted file mode 100644 index be122bd18..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/MASA.Contrib.DDD.Domain.csproj +++ /dev/null @@ -1,18 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - diff --git a/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs b/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs deleted file mode 100644 index 742ffa1bc..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/Options/DispatcherOptions.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace MASA.Contrib.DDD.Domain; - -public class DispatcherOptions : IDispatcherOptions -{ - private Assembly[] _assemblies = new Assembly[0]; - - public Assembly[] Assemblies - { - get => _assemblies; - set - { - _assemblies = value; - if (_assemblies == null || _assemblies.Length == 0) - { - throw new ArgumentNullException(nameof(_assemblies)); - } - Types = _assemblies.SelectMany(assembly => assembly.GetTypes()); - AllEventTypes = GetTypes(typeof(IEvent)).ToList(); - AllDomainServiceTypes = GetTypes(typeof(DomainService)).ToList(); - AllAggregateRootTypes = GetTypes(typeof(IAggregateRoot)).Where(type => IsAggregateRootEntity(type)).ToList(); - } - } - - private bool IsAggregateRootEntity(Type type) - => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && typeof(IAggregateRoot).IsAssignableFrom(type); - - private IEnumerable Types { get; set; } - - private IEnumerable GetTypes(Type type) => Types.Where(t => t.IsClass && type.IsAssignableFrom(t)); - - internal List AllEventTypes { get; private set; } - - internal List AllDomainServiceTypes { get; private set; } - - internal List AllAggregateRootTypes { get; private set; } - - public IServiceCollection Services { get; } - - public DispatcherOptions(IServiceCollection services) => Services = services; -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/README.md b/src/DDD/MASA.Contrib.DDD.Domain/README.md deleted file mode 100644 index 842995a1e..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/README.md +++ /dev/null @@ -1,119 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -### DomainEventBus - -Example๏ผš - -```c# -Install-Package MASA.Contrib.DDD.Domain -Install-Package MASA.Contrib.DDD.Domain.Repository.EF - -Install-Package MASA.Contrib.Dispatcher.Events - -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF -Install-Package MASA.Contrib.Data.UoW.EF -``` - -1. Add DomainEventBus - -```C# -builder.Services -.AddDomainEventBus(options => -{ - options.UseEventBus()//Use in-process events - .UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) - .UseDaprEventBus()///Use cross-process events - .UseEventLog() - .UseRepository();//Use the EF version of Repository to achieve -}) -``` - -2. Add DomainCommand - -```C# -public class RegisterUserDomainCommand : DomainCommand -{ - public string User { get; set; } = default!; - - public string Password { get; set; } = default!; - - public string PhoneNumber { get; set; } = default!; -} -``` -> DomainQuery refers to Query in CQRS - -3. Add Handler (in process) - -```C# -public class UserHandler -{ - [EventHandler] - public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) - { - //TODO Registered user business - } -} -``` - -4. Send DomainCommand - -```C# -IDomainEventBus eventBus;//Get IDomainEventBus through DI -await eventBus.PublishAsync(new RegisterUserDomainCommand());//Send DomainCommand -``` - -5. Define domain events - -```C# -public class RegisterUserSucceededIntegrationEvent : IntegrationDomainEvent -{ - public override string Topic { get; set; } = nameof(RegisterUserSucceededIntegrationEvent); - - public string Account { get; set; } = default!; -} - -public class RegisterUserSucceededEvent : DomainEvent -{ - public string Account { get; set; } = default!; -} -``` - -6. Define domain services - -```C# -public class UserDomainService : DomainService -{ - public UserDomainService(IDomainEventBus eventBus) : base(eventBus) - { - } - - public async Task RegisterSucceededAsync(string account) - { - await EventBus.Enqueue(new RegisterUserSucceededIntegrationEvent() { Account = account }); - await EventBus.Enqueue(new RegisterUserSucceededEvent() { Account = account }); - await EventBus.PublishQueueAsync(); - } -} -``` - -> DomainEvent (in-process) and IntegrationDomainEvent (cross-process) can be inherited as needed -> -> If you only need to send a domain event, you can directly use EventBus.PublishQueueAsync(new RegisterUserSucceededEvent()) -> -> If you want to send in a unified way, you can send it through EventBus.Enqueue() and finally call EventBus.PublishQueueAsync() - -Tip๏ผš - -> 1. The use of DomainEventBus must require the implementation of IEventBus and IIntegrationEventBus and IUnitOfWork -> 2. EventBus only supports in-process scheduling, cross-process scheduling is not supported, and the sending order is consistent with the enqueue order, but the actual execution order is unknown - -7. Cross-process event subscription - -```C# -[Topic("pubsub", nameof(RegisterUserSucceededIntegrationEvent))] -public async Task RegisterUserSucceededHandlerAsync(RegisterUserSucceededIntegrationEvent @event) -{ - //todo -} -``` \ No newline at end of file diff --git a/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md b/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md deleted file mode 100644 index a6402cc4d..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/README.zh-CN.md +++ /dev/null @@ -1,119 +0,0 @@ -ไธญ | [EN](README.md) - -### DomainEventBus - -็”จไพ‹๏ผš - -```c# -Install-Package MASA.Contrib.DDD.Domain -Install-Package MASA.Contrib.DDD.Domain.Repository.EF - -Install-Package MASA.Contrib.Dispatcher.Events - -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF -Install-Package MASA.Contrib.Data.UoW.EF -``` - -1. ๆทปๅŠ DomainEventBus - -```C# -builder.Services -.AddDomainEventBus(options => -{ - options.UseEventBus()//ไฝฟ็”จ่ฟ›็จ‹ๅ†…ไบ‹ไปถ - .UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) - .UseDaprEventBus()///ไฝฟ็”จ่ทจ่ฟ›็จ‹ไบ‹ไปถ - .UseEventLog() - .UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ -}) -``` - -2. ๆทปๅŠ DomainCommand - -```C# -public class RegisterUserDomainCommand : DomainCommand -{ - public string User { get; set; } = default!; - - public string Password { get; set; } = default!; - - public string PhoneNumber { get; set; } = default!; -} -``` -> DomainQueryๅ‚่€ƒCQRSไธญ็š„Query - -3. ๆทปๅŠ Handler๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ - -```C# -public class UserHandler -{ - [EventHandler] - public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) - { - //TODO ๆณจๅ†Œ็”จๆˆทไธšๅŠก - } -} -``` - -4. ๅ‘้€DomainCommand - -```C# -IDomainEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIDomainEventBus -await eventBus.PublishAsync(new RegisterUserDomainCommand());//ๅ‘้€DomainCommand -``` - -5. ๅฎšไน‰้ข†ๅŸŸไบ‹ไปถ - -```C# -public class RegisterUserSucceededIntegrationEvent : IntegrationDomainEvent -{ - public override string Topic { get; set; } = nameof(RegisterUserSucceededIntegrationEvent); - - public string Account { get; set; } = default!; -} - -public class RegisterUserSucceededEvent : DomainEvent -{ - public string Account { get; set; } = default!; -} -``` - -6. ๅฎšไน‰้ข†ๅŸŸๆœๅŠก - -```C# -public class UserDomainService : DomainService -{ - public UserDomainService(IDomainEventBus eventBus) : base(eventBus) - { - } - - public async Task RegisterSucceededAsync(string account) - { - await EventBus.Enqueue(new RegisterUserSucceededIntegrationEvent() { Account = account }); - await EventBus.Enqueue(new RegisterUserSucceededEvent() { Account = account }); - await EventBus.PublishQueueAsync(); - } -} -``` - -> ๅฏๆ นๆฎ้œ€่ฆ็ปงๆ‰ฟDomainEvent๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ใ€IntegrationDomainEvent๏ผˆ่ทจ่ฟ›็จ‹๏ผ‰ -> -> ๅฆ‚ๆžœๅช้œ€่ฆๅ‘้€ไธ€ไธช้ข†ๅŸŸไบ‹ไปถ๏ผŒๅˆ™ๅฏไปฅ็›ดๆŽฅไฝฟ็”จEventBus.PublishQueueAsync(new RegisterUserSucceededEvent())ๅณๅฏ -> -> ๅฆ‚ๆžœๅธŒๆœ›็ปŸไธ€ๅ‘้€๏ผŒๅˆ™ๅฏไปฅ้€š่ฟ‡EventBus.Enqueue()ใ€ๆœ€ๅŽ่ฐƒ็”จEventBus.PublishQueueAsync()ๅ‘้€ - -ๆ็คบ๏ผš - -> 1. ไฝฟ็”จDomainEventBusๅฟ…้กป่ฆๆฑ‚ๅฎž็ŽฐIEventBusไปฅๅŠIIntegrationEventBusไปฅๅŠIUnitOfWork -> 2. EventBusๅชๆ”ฏๆŒ่ฟ›็จ‹ๅ†…็ผ–ๆŽ’ใ€่ทจ่ฟ›็จ‹ไธๆ”ฏๆŒ็ผ–ๆŽ’๏ผŒๅ‘้€้กบๅบไธŽๅ…ฅ้˜Ÿ้กบๅบไธ€่‡ด๏ผŒไฝ†ๅฎž้™…ๆ‰ง่กŒ้กบๅบๆœช็Ÿฅ - -7. ่ทจ่ฟ›็จ‹ไบ‹ไปถ่ฎข้˜… - -```C# -[Topic("pubsub", nameof(RegisterUserSucceededIntegrationEvent))] -public async Task RegisterUserSucceededHandlerAsync(RegisterUserSucceededIntegrationEvent @event) -{ - //todo -} -``` diff --git a/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs b/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs deleted file mode 100644 index 28c03d33e..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace MASA.Contrib.DDD.Domain; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddDomainEventBus( - this IServiceCollection services, - Action? options = null) - { - if (services.Any(service => service.ImplementationType == typeof(DomainEventBusProvider))) - return services; - - services.AddSingleton(); - - var dispatcherOptions = new DispatcherOptions(services); - options?.Invoke(dispatcherOptions); - if (dispatcherOptions.Assemblies.Length == 0) - { - dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); - } - services.AddSingleton(typeof(IOptions), serviceProvider => Options.Create(dispatcherOptions)); - - if (services.All(service => service.ServiceType != typeof(IEventBus))) - { - throw new Exception("Please add EventBus first."); - } - - if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) - { - throw new Exception("Please add UoW first."); - } - - if (services.All(service => service.ServiceType != typeof(IIntegrationEventBus))) - { - throw new Exception("Please add IntegrationEventBus first."); - } - - services.CheckAggregateRootRepositories(dispatcherOptions.AllAggregateRootTypes); - - foreach (var domainServiceType in dispatcherOptions.AllDomainServiceTypes) - { - services.TryAddScoped(domainServiceType); - } - - services.TryAddScoped(); - services.TryAddScoped(); - return services; - } - - private static void CheckAggregateRootRepositories(this IServiceCollection services, List aggregateRootRepositoryTypes) - { - foreach (var aggregateRootRepositoryType in aggregateRootRepositoryTypes) - { - var serviceType = GetServiceRepositoryType(aggregateRootRepositoryType); - if (services.All(service => service.ServiceType != serviceType)) - { - throw new NotImplementedException($"The number of types of {serviceType.FullName} implementation class must be 1."); - } - } - } - - private static Type GetServiceRepositoryType(Type entityType) => typeof(IRepository<>).MakeGenericType(entityType); - - private class DomainEventBusProvider - { - - } -} diff --git a/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs b/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs deleted file mode 100644 index 51c99d1c7..000000000 --- a/src/DDD/MASA.Contrib.DDD.Domain/_Imports.cs +++ /dev/null @@ -1,15 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.DDD.Domain.Entities; -global using MASA.BuildingBlocks.DDD.Domain.Events; -global using MASA.BuildingBlocks.DDD.Domain.Repositories; -global using MASA.BuildingBlocks.DDD.Domain.Services; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.Contrib.DDD.Domain.Internal; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Options; -global using System.Collections.Concurrent; -global using System.Linq.Expressions; -global using System.Reflection; -global using System.Text.Json.Serialization; diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj b/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj deleted file mode 100644 index 4219b95ea..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/MASA.Contrib.Data.Contracts.EF.csproj +++ /dev/null @@ -1,19 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/README.md b/src/Data/MASA.Contrib.Data.Contracts.EF/README.md deleted file mode 100644 index 04282f6fe..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/README.md +++ /dev/null @@ -1,31 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## Contracts.EF - -Example๏ผš - -```C# -Install-Package MASA.Contrib.Data.UoW.EF -Install-Package MASA.Contrib.Data.Contracts.EF -``` - -```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => - { - dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - dbOptions.UseSoftDelete(builder.Services); - }); -}); -``` - -> When the entity inherits ISoftware and is deleted, change the delete state to the modified state, and cooperate with the custom Remove operation to achieve soft deletion -> Do not query the data marked as soft deleted when querying - -> Frequently Asked Questions: - -- Problem 1: After using UseSoftDelete, there is a problem that the submission cannot be saved - - After using Uow, the transaction will be enabled by default after Addใ€ Modifiedใ€ and Deleted - and the transaction can be saved normally after the transaction is submitted - If the EventBus is used, the transaction will be automatically submitted \ No newline at end of file diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md b/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md deleted file mode 100644 index 5de5f31ae..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/README.zh-CN.md +++ /dev/null @@ -1,31 +0,0 @@ -ไธญ | [EN](README.md) - -## Contracts.EF - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Data.UoW.EF -Install-Package MASA.Contrib.Data.Contracts.EF -``` - -```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => - { - dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - dbOptions.UseSoftDelete(builder.Services);//ๅฏๅŠจ่ฝฏๅˆ ้™ค - }); -}); -``` - -> ๅฝ“ๅฎžไฝ“็ปงๆ‰ฟISoftware๏ผŒไธ”่ขซๅˆ ้™คๆ—ถ๏ผŒๅฐ†ๅˆ ้™ค็Šถๆ€ๆ”นไธบไฟฎๆ”น็Šถๆ€๏ผŒๅนถ้…ๅˆ่‡ชๅฎšไน‰Removeๆ“ไฝœ๏ผŒๅฎž็Žฐ่ฝฏๅˆ ้™ค -> ๆ”ฏๆŒๆŸฅ่ฏข็š„ๆ—ถๅ€™ไธๆŸฅ่ฏข่ขซๆ ‡่ฎฐ่ฝฏๅˆ ้™ค็š„ๆ•ฐๆฎ - -> ๅธธ่ง้—ฎ้ข˜๏ผš - -- ้—ฎ้ข˜1๏ผšไฝฟ็”จUseSoftDeleteๅŽๅ‡บ็Žฐๆไบคไฟๅญ˜ไธไธŠ็š„้—ฎ้ข˜ - - ไฝฟ็”จUowๅŽ๏ผŒ้ป˜่ฎคๅœจ่ฟ›่กŒAddใ€Modifiedใ€DeletedๅŽไผšๅฏ็”จไบ‹ๅŠก - ้œ€่ฆๆไบคไบ‹ๅŠกไน‹ๅŽๆ‰่ƒฝๆญฃๅธธไฟๅญ˜ - ๅฆ‚ๆžœไฝฟ็”จEventBusๅˆ™ไผš่‡ชๅŠจๆไบคไบ‹ๅŠก \ No newline at end of file diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs deleted file mode 100644 index d15233ea7..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MASA.Contrib.Data.Contracts.EF; - -public static class ServiceCollectionExtensions -{ - public static MasaDbContextOptionsBuilder UseSoftDelete( - this MasaDbContextOptionsBuilder optionsBuilder, IServiceCollection services) - { - if (services.Any(s => s.ImplementationType == typeof(ContractsFilter))) return optionsBuilder; - services.AddSingleton(); - - if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) - throw new Exception("Please add UoW first."); - - optionsBuilder.UseQueryFilterProvider() - .UseSaveChangesFilter(); - - return optionsBuilder; - } - - private class ContractsFilter - { - } -} diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs deleted file mode 100644 index 4b25d972c..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MASA.Contrib.Data.Contracts.EF.SoftDelete; - -public class QueryFilterProvider : IQueryFilterProvider -{ - public LambdaExpression OnExecuting(IMutableEntityType entityType) - { - var parameter = Expression.Parameter(entityType.ClrType); - if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType)) - { - var propertyMethodInfo = typeof(Microsoft.EntityFrameworkCore.EF).GetMethod("Property")!.MakeGenericMethod(typeof(bool)); - var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant(nameof(ISoftDelete.IsDeleted))); - var compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false)); - var lambda = Expression.Lambda(compareExpression, parameter); - return lambda; - } - - return default!; - } -} diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs deleted file mode 100644 index da0023f04..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MASA.Contrib.Data.Contracts.EF.SoftDelete; - -public class SoftDeleteSaveChangesFilter : ISaveChangesFilter -{ - public void OnExecuting(ChangeTracker changeTracker) - { - changeTracker.DetectChanges(); - foreach (var entity in changeTracker.Entries().Where(e => e.State == Microsoft.EntityFrameworkCore.EntityState.Deleted)) - { - if (entity.Entity is ISoftDelete) - { - entity.State = Microsoft.EntityFrameworkCore.EntityState.Modified; - entity.CurrentValues[nameof(ISoftDelete.IsDeleted)] = true; - } - } - } -} diff --git a/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs b/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs deleted file mode 100644 index d4c3c90c7..000000000 --- a/src/Data/MASA.Contrib.Data.Contracts.EF/_Imports.cs +++ /dev/null @@ -1,9 +0,0 @@ -global using MASA.BuildingBlocks.Data.Contracts; -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.Contrib.Data.Contracts.EF.SoftDelete; -global using MASA.Utils.Data.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.ChangeTracking; -global using Microsoft.EntityFrameworkCore.Metadata; -global using Microsoft.Extensions.DependencyInjection; -global using System.Linq.Expressions; diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs deleted file mode 100644 index b549686ef..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace MASA.Contrib.Data.UoW.EF; - -public static class DispatcherOptionsExtensions -{ - public static IDispatcherOptions UseUoW( - this IDispatcherOptions options, - Action? optionsBuilder = null, - bool disableRollbackOnFailure = false, - bool useTransaction = true) - where TDbContext : MasaDbContext - { - if (options.Services == null) - throw new ArgumentNullException(nameof(options.Services)); - - if (options.Services.Any(service => service.ImplementationType == typeof(UoWProvider))) - return options; - - options.Services.AddSingleton(); - - options.Services.AddScoped(serviceProvider => - { - var dbContext = serviceProvider.GetRequiredService(); - var logger = serviceProvider.GetService>>(); - return new UnitOfWork(dbContext, logger) - { - DisableRollbackOnFailure = disableRollbackOnFailure, - UseTransaction = useTransaction - }; - }); - if (options.Services.All(service => service.ServiceType != typeof(MasaDbContextOptions))) - options.Services.AddMasaDbContext(optionsBuilder); - - options.Services.AddScoped(); - - return options; - } - - private class UoWProvider - { - - } -} - diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj b/src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj deleted file mode 100644 index afd4da368..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/MASA.Contrib.Data.UoW.EF.csproj +++ /dev/null @@ -1,20 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - - diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/README.md b/src/Data/MASA.Contrib.Data.UoW.EF/README.md deleted file mode 100644 index efd89e947..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/README.md +++ /dev/null @@ -1,20 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## Contracts.EF - -Example๏ผš - -```C# -Install-Package MASA.Contrib.Data.UoW.EF -Install-Package MASA.Contrib.Data.Contracts.EF -``` - -```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => - { - dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - }); -}); -``` - diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md b/src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md deleted file mode 100644 index 316b94e90..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/README.zh-CN.md +++ /dev/null @@ -1,20 +0,0 @@ -ไธญ | [EN](README.md) - -## Contracts.EF - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Data.UoW.EF -Install-Package MASA.Contrib.Data.Contracts.EF -``` - -```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => - { - dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); - }); -}); -``` - diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs b/src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs deleted file mode 100644 index f7fdf6f2b..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/Transaction.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MASA.Contrib.Data.UoW.EF; - -public class Transaction : ITransaction -{ - public Transaction(IUnitOfWork unitOfWork) => UnitOfWork = unitOfWork; - - [JsonIgnore] - public IUnitOfWork? UnitOfWork { get; set; } -} diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs b/src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs deleted file mode 100644 index eaacfefac..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/UnitOfWork.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace MASA.Contrib.Data.UoW.EF; - -public class UnitOfWork : IAsyncDisposable, IUnitOfWork - where TDbContext : MasaDbContext -{ - public DbTransaction Transaction - { - get - { - if (!UseTransaction) - throw new NotSupportedException("Doesn't support transaction opening"); - - if (TransactionHasBegun) - return _context.Database.CurrentTransaction!.GetDbTransaction(); - - return _context.Database.BeginTransaction().GetDbTransaction(); - } - } - - public bool TransactionHasBegun => _context.Database.CurrentTransaction != null; - - public bool DisableRollbackOnFailure { get; set; } - - public EntityState EntityState { get; set; } - - public CommitState CommitState { get; set; } - - public bool UseTransaction { get; set; } = true; - - private readonly DbContext _context; - - private readonly ILogger>? _logger; - - public UnitOfWork(TDbContext dbContext, ILogger>? logger = null) - { - _context = dbContext; - _logger = logger; - } - - public async Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - await _context.SaveChangesAsync(cancellationToken); - EntityState = EntityState.Unchanged; - } - - public async Task CommitAsync(CancellationToken cancellationToken = default) - { - if (!UseTransaction || !TransactionHasBegun) - throw new NotSupportedException("Transaction not opened"); - - await _context.Database.CommitTransactionAsync(cancellationToken); - CommitState = CommitState.Commited; - } - - public async Task RollbackAsync(CancellationToken cancellationToken = default) - { - if (!UseTransaction || !TransactionHasBegun) - throw new NotSupportedException("Transactions are not opened and rollback is not supported"); - - await _context.Database.RollbackTransactionAsync(cancellationToken); - } - - public ValueTask DisposeAsync() => _context.DisposeAsync(); - - public void Dispose() => _context.Dispose(); -} diff --git a/src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs b/src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs deleted file mode 100644 index cfd1ca3f9..000000000 --- a/src/Data/MASA.Contrib.Data.UoW.EF/_Imports.cs +++ /dev/null @@ -1,11 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Utils.Data.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.Storage; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; -global using System.Data.Common; -global using System.Text.Json.Serialization; -global using EntityState = MASA.BuildingBlocks.Data.UoW.EntityState; - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs deleted file mode 100644 index f4f7c2aa3..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events; - -public static class DispatcherOptionsExtensions -{ - public static IDispatcherOptions UseEventBus(this IDispatcherOptions options) - => options.UseEventBus(AppDomain.CurrentDomain.GetAssemblies()); - - public static IDispatcherOptions UseEventBus( - this IDispatcherOptions options, - params Assembly[] assemblies) - => options.UseEventBus(ServiceLifetime.Scoped, assemblies); - - public static IDispatcherOptions UseEventBus( - this IDispatcherOptions options, - ServiceLifetime lifetime, - params Assembly[] assemblies) - { - if (options.Services == null) - { - throw new ArgumentNullException(nameof(options.Services)); - } - - options.Services.AddEventBus(lifetime, options => options.Assemblies = assemblies); - return options; - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Enums/FailureLevels.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Enums/FailureLevels.cs deleted file mode 100644 index 65b30e242..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Enums/FailureLevels.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Enums; - -public enum FailureLevels -{ - Throw = 1, - - ThrowAndCancel, - - Ignore -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs deleted file mode 100644 index 9431ab1d1..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Event.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events; - -public record Event(Guid Id, DateTime CreationTime) : IEvent -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - public Event() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs deleted file mode 100644 index 1f7cd0cd8..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventBus.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events; - -public class EventBus : IEventBus -{ - private readonly IServiceProvider _serviceProvider; - - private readonly Internal.Dispatch.Dispatcher _dispatcher; - - private readonly DispatcherOptions _options; - - private IUnitOfWork? _unitOfWork; - - private readonly string LoadEventHelpLink = "https://github.com/masastack/MASA.Contrib/tree/develop/docs/LoadEvent.md"; - - public EventBus(IServiceProvider serviceProvider, IOptions options) - { - _serviceProvider = serviceProvider; - _dispatcher = serviceProvider.GetRequiredService(); - _options = options.Value; - } - - public async Task PublishAsync(TEvent @event) where TEvent : IEvent - { - var eventType = typeof(TEvent); - if (@event is null) - { - throw new ArgumentNullException(eventType.Name); - } - - var middlewares = _serviceProvider.GetRequiredService>>(); - if (!_options.UnitOfWorkRelation.ContainsKey(eventType)) - { - throw new NotSupportedException($"Getting \"{eventType.Name}\" relationship chain failed, see {LoadEventHelpLink} for details. "); - } - - if (_options.UnitOfWorkRelation[eventType]) - { - ITransaction transactionEvent = (ITransaction) @event; - var unitOfWork = _serviceProvider.GetService(); - if (unitOfWork != null) - { - transactionEvent.UnitOfWork = unitOfWork; - if (_unitOfWork is null) - { - _unitOfWork = transactionEvent.UnitOfWork; - } - else - { - middlewares = middlewares.Where(middleware => middleware is not TransactionMiddleware); - } - } - } - - EventHandlerDelegate publishEvent = async () => { await _dispatcher.PublishEventAsync(_serviceProvider, @event); }; - await middlewares.Reverse().Aggregate(publishEvent, (next, middleware) => () => middleware.HandleAsync(@event, next))(); - } - - public IEnumerable GetAllEventTypes() => _options.AllEventTypes; - - public async Task CommitAsync(CancellationToken cancellationToken = default) - { - if (_unitOfWork is null) - throw new ArgumentNullException("You need to UseUoW when adding services"); - - await _unitOfWork.CommitAsync(cancellationToken); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventHandlerAttribute.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventHandlerAttribute.cs deleted file mode 100644 index 90b839c0d..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/EventHandlerAttribute.cs +++ /dev/null @@ -1,150 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events; - -[AttributeUsage(AttributeTargets.Method)] -public class EventHandlerAttribute : Attribute -{ - public EventHandlerAttribute() : this(DefaultOrder) - { - } - - public EventHandlerAttribute(int order) : this(order, false) - { - - } - - public EventHandlerAttribute(int order, bool enableRetry) : this(order, enableRetry, enableRetry ? DefaultRetryCount : 0) - { - - } - - public EventHandlerAttribute(int order, FailureLevels failureLevels) : this(order, failureLevels, false) - { - - } - - public EventHandlerAttribute(int order, bool enableRetry, int retryTimes) : this(order, FailureLevels.Throw, enableRetry, retryTimes) - { - - } - - public EventHandlerAttribute(int order, bool enableRetry, bool isCancel) : this(order, enableRetry, isCancel, enableRetry ? DefaultRetryCount : 0) - { - - } - - public EventHandlerAttribute(int order, bool enableRetry, bool isCancel, int retryTimes) : this(order, FailureLevels.Throw, enableRetry, retryTimes, isCancel) - { - - } - - public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry) : this(order, failureLevels, enableRetry, enableRetry ? DefaultRetryCount : 0) - { - - } - - public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry, bool isCancel) : this(order, failureLevels, enableRetry, enableRetry ? DefaultRetryCount : 0, isCancel) - { - - } - - public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry, int retryTimes) : this(order, failureLevels, enableRetry, retryTimes, false) - { - - } - - public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry, int retryTimes, bool isCancel) - { - Order = order; - FailureLevels = failureLevels; - EnableRetry = enableRetry; - RetryTimes = enableRetry ? retryTimes : 0; - IsCancel = isCancel; - } - - /// - /// The default number of retry attempts. - /// - private const int DefaultRetryCount = 3; - - private const int DefaultOrder = int.MaxValue; - - /// - /// Used to control the order in which methods are executed, in ascending order. default is 100 - /// Must be greater than or equal to 0 - /// - public int Order { get; set; } - - public FailureLevels FailureLevels { get; set; } - - public bool EnableRetry { get; set; } - - /// - /// The default value is 3๏ผŒEnableRetry must be true to take effect - /// - public int RetryTimes { get; set; } - - /// - /// Used to cancel or compensate - /// - public bool IsCancel { get; set; } - - internal MethodInfo ActionMethodInfo { get; set; } - - internal Type InstanceType { get; set; } - - internal Type EventType { get; set; } - - internal int ActualRetryTimes => EnableRetry ? RetryTimes : 0; - - internal TaskInvokeDelegate InvokeDelegate { get; private set; } - - private object Instance { get; set; } = default!; - - private object EventHandler { get; set; } - - internal bool IsEventHandler => FailureLevels == FailureLevels.Throw || FailureLevels == FailureLevels.ThrowAndCancel; - - internal void BuildExpression() - { - InvokeDelegate = InvokeBuilder.Build(ActionMethodInfo, InstanceType); - } - - internal async Task ExcuteAction(IServiceProvider serviceProvider, TEvent @event) where TEvent : IEvent - { - if (InvokeDelegate != null) - { - Instance = serviceProvider.GetRequiredService(InstanceType); - await InvokeDelegate.Invoke(Instance, @event); - } - else - { - await ExcuteSagaAction(serviceProvider, @event); - } - } - - private async Task ExcuteSagaAction(IServiceProvider serviceProvider, TEvent @event) where TEvent : IEvent - { - if (!IsCancel) - { - if (EventHandler == null) - { - var handlers = serviceProvider.GetServices>(); - var handler = handlers.Where(x => x.GetType().Equals(InstanceType)).FirstOrDefault()!; - EventHandler = handler; - } - await ((IEventHandler)EventHandler).HandleAsync(@event); - } - else - { - if (EventHandler == null) - { - var handlers = serviceProvider.GetServices>(); - var handler = handlers.Where(x => x.GetType().Equals(InstanceType)).FirstOrDefault()!; - EventHandler = handler; - } - await ((ISagaEventHandler)EventHandler).CancelAsync(@event); - } - } - - internal bool IsHandlerMissing(int maxCancelOrder) => FailureLevels == FailureLevels.ThrowAndCancel && Order < maxCancelOrder || FailureLevels == FailureLevels.Throw && Order <= maxCancelOrder; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs deleted file mode 100644 index 645c988a3..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; - -internal class DispatchRelationNetwork -{ - public Dictionary> RelationNetwork { get; set; } = new(); - - public Dictionary> HandlerRelationNetwork { get; set; } = new(); - - public Dictionary> CancelRelationNetwork { get; set; } = new(); - - private readonly ILogger? _logger; - - public DispatchRelationNetwork(ILogger? logger) => _logger = logger; - - public void Add(Type keyEventType, EventHandlerAttribute handler) - { - Add(keyEventType, handler, !handler.IsCancel ? HandlerRelationNetwork : CancelRelationNetwork); - } - - /// - /// If the relationship does not exist in the network, add it - /// - /// - /// - /// - private void Add(Type keyEventType, - EventHandlerAttribute handlers, - Dictionary> dispatchRelativeNetwork) - { - if (!dispatchRelativeNetwork.ContainsKey(keyEventType)) - { - dispatchRelativeNetwork.Add(keyEventType, new List()); - } - - if (!dispatchRelativeNetwork[keyEventType].Any(x => x.ActionMethodInfo.Equals(handlers.ActionMethodInfo) && x.InstanceType.Equals(handlers.InstanceType))) - { - dispatchRelativeNetwork[keyEventType].Add(handlers); - } - } - - public void Build() - { - Sort(); - CheckConstraints(); - RelationNetwork = HandlerRelationNetwork.ToDictionary(relationNetwork => relationNetwork.Key, - relationNetwork => relationNetwork.Value.Select(handler => new DispatchRelationOptions(handler)).ToList()); - - foreach (var relation in RelationNetwork) - { - foreach (var relationOption in RelationNetwork[relation.Key]!) - { - if (CancelRelationNetwork.TryGetValue(relation.Key, out List? cancelRelations)) - { - var cancelHandlers = cancelRelations.TakeWhile(handler => relationOption.IsCancelHandler(relationOption.Handler)).Reverse().ToList(); - relationOption.AddCancelHandler(cancelHandlers); - } - } - } - } - - private void Sort() - { - HandlerRelationNetwork = Sort(HandlerRelationNetwork); - CancelRelationNetwork = Sort(CancelRelationNetwork); - } - - private Dictionary> Sort(Dictionary> dispatchRelatives) - where TDispatchHandlerAttribute : EventHandlerAttribute - { - return dispatchRelatives.ToDictionary( - dispatchRelative => dispatchRelative.Key, - dispatchRelative => dispatchRelative.Value.OrderBy(attr => attr.Order).ToList() - ); - } - - /// - /// Checking scheduling Relationships - /// Throw an exception for a Handler that only has Cancel - /// and warn a Handler that the Cancel will never perform because the Order is improperly set - /// - private void CheckConstraints() - { - foreach (var cancelRelation in CancelRelationNetwork) - { - if (HandlerRelationNetwork.All(relation => relation.Key != cancelRelation.Key)) - { - throw new NotSupportedException($"{cancelRelation.Key.Name} is only have a cancel handler, it must have an event handler."); - } - - var maxCancelOrder = cancelRelation.Value.Max(handler => handler.Order); - var maxHandlerOrder = HandlerRelationNetwork[cancelRelation.Key].Where(handler => handler.IsEventHandler).OrderByDescending(handler => handler.Order).ThenByDescending(handler => handler.FailureLevels).FirstOrDefault(); - if (maxHandlerOrder == null || maxHandlerOrder.IsHandlerMissing(maxCancelOrder)) - { - var methodName = cancelRelation.Value.Select(x => x.ActionMethodInfo.Name).LastOrDefault(); - _logger?.LogWarning($"The {methodName} method is meaningless, because its Order attribute is too large, and no handler corresponding to the Order can be triggered. It is suggested to lower the Order attribute of {methodName} or add a matching handler - {cancelRelation.Value.Select(x => x.InstanceType.FullName).LastOrDefault()}"); - } - } - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs deleted file mode 100644 index 412d5ab03..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; - -internal class Dispatcher : DispatcherBase -{ - public Dispatcher(IServiceCollection services, Assembly[] assemblies, bool forceInit = false) : base(services, assemblies, forceInit) { } - - public Dispatcher Build(ServiceLifetime lifetime) - { - foreach (var assembly in _assemblies) - { - AddRelationNetwork(assembly); - } - foreach (var dispatchInstance in GetAddServiceTypeList()) - { - _services.Add(dispatchInstance, dispatchInstance, lifetime); - } - Build(); - return this; - } - - private void AddRelationNetwork(Assembly assembly) - { - foreach (var type in assembly.GetTypes()) - { - if (!type.IsConcrete()) - { - continue;//Handler and Cancel must be normal classes, not abstract classes or interfaces - } - - foreach (var method in type.GetMethods()) - { - AddRelationNetwork(type, method); - } - } - } - - private void AddRelationNetwork(Type type, MethodInfo method) - { - var attribute = method.GetCustomAttributes(typeof(EventHandlerAttribute), true).FirstOrDefault(); - var handler = attribute as EventHandlerAttribute; - if (attribute is not null && handler is not null) - { - var parameters = method.GetParameters(); - if (parameters == null || - parameters.Length != 1 || - !parameters.Any(parameter => typeof(IEvent).IsAssignableFrom(parameter.ParameterType))) - { - throw new ArgumentOutOfRangeException(string.Format("[{0}] must have only one argument and inherit from Event", method.Name)); - } - if (IsSagaMode(type, method)) - { - return; - } - - if (handler.Order < 0) - { - throw new ArgumentOutOfRangeException("The order must be greater than or equal to 0"); - } - - var parameter = parameters.FirstOrDefault()!; - handler.ActionMethodInfo = method; - handler.InstanceType = type; - handler.EventType = parameter.ParameterType; - handler.BuildExpression(); - AddRelationNetwork(parameter.ParameterType, handler); - } - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs deleted file mode 100644 index 7074e5be1..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs +++ /dev/null @@ -1,128 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; - -internal class DispatcherBase -{ - protected static DispatchRelationNetwork? _sharingRelationNetwork; - - protected readonly IServiceCollection _services; - - protected readonly Assembly[] _assemblies; - - private readonly ILogger? _logger; - - public DispatcherBase(IServiceCollection services, Assembly[] assemblies, bool forceInit) - { - _services = services; - _assemblies = assemblies; - var serviceProvider = services.BuildServiceProvider(); - if (_sharingRelationNetwork == null || forceInit) - { - _sharingRelationNetwork = new DispatchRelationNetwork(serviceProvider.GetService>()); - } - _logger = serviceProvider.GetService>(); - } - - public async Task PublishEventAsync(IServiceProvider serviceProvider, TEvent @event) - where TEvent : IEvent - { - var eventType = typeof(TEvent); - if (!_sharingRelationNetwork!.RelationNetwork.TryGetValue(eventType, out List? dispatchRelations)) - { - if (@event is IIntegrationEvent) - { - _logger?.LogError($"Dispatcher: The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); - throw new ArgumentNullException($"The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); - } - - _logger?.LogError($"Dispatcher: The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); - throw new ArgumentNullException($"The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); - } - await ExecuteEventHandlerAsync(serviceProvider, dispatchRelations, @event); - } - - private async Task ExecuteEventHandlerAsync(IServiceProvider serviceProvider, - List dispatchRelations, - TEvent @event) - where TEvent : IEvent - { - var executionStrategy = serviceProvider.GetRequiredService(); - StrategyOptions strategyOptions = new StrategyOptions(); - bool isCancel = false; - EventHandlerAttribute dispatchHandler; - foreach (var dispatchRelation in dispatchRelations) - { - if (isCancel) return; - dispatchHandler = dispatchRelation.Handler; - - strategyOptions.SetStrategy(dispatchHandler); - - await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => - { - _logger?.LogDebug("----- Publishing event {@Event}: message id: {messageId} -----", @event, @event.Id); - await dispatchHandler.ExcuteAction(serviceProvider, @event); - }, async (@event, ex, failureLevels) => - { - if (failureLevels != FailureLevels.Ignore) - { - isCancel = true; - if (dispatchRelation.CancelHandlers.Any()) - { - await ExecuteEventCanceledHandlerAsync(serviceProvider, _logger, executionStrategy, dispatchRelation.CancelHandlers, @event); - } - else - { - throw ex; - } - } - else - { - _logger?.LogWarning("----- Publishing event {@Event} error rollback is ignored: message id: {messageId} -----", @event, @event.Id); - } - }); - } - } - - private async Task ExecuteEventCanceledHandlerAsync(IServiceProvider serviceProvider, - ILogger? logger, - IExecutionStrategy executionStrategy, - IEnumerable cancelHandlers, - TEvent @event) - where TEvent : IEvent - { - StrategyOptions strategyOptions = new StrategyOptions(); - foreach (var cancelHandler in cancelHandlers) - { - strategyOptions.SetStrategy(cancelHandler); - await executionStrategy.ExecuteAsync(strategyOptions, @event, async @event => - { - logger?.LogDebug("----- Publishing event {@Event} rollback start: message id: {messageId} -----", @event, @event.Id); - await cancelHandler.ExcuteAction(serviceProvider, @event); - }, (@event, ex, failureLevels) => - { - if (failureLevels != FailureLevels.Ignore) - { - throw ex; - } - logger?.LogWarning("----- Publishing event {@Event} rollback error ignored: message id: {messageId} -----", @event, @event.Id); - return Task.CompletedTask; - }); - } - } - - protected void AddRelationNetwork(Type parameterType, EventHandlerAttribute handler) - { - _sharingRelationNetwork!.Add(parameterType, handler); - } - - protected IEnumerable GetAddServiceTypeList() => _sharingRelationNetwork!.HandlerRelationNetwork - .Concat(_sharingRelationNetwork.CancelRelationNetwork) - .SelectMany(relative => relative.Value) - .Where(dispatchHandler => dispatchHandler.InvokeDelegate != null) - .Select(dispatchHandler => dispatchHandler.InstanceType).Distinct(); - - protected void Build() => _sharingRelationNetwork!.Build(); - - protected bool IsSagaMode(Type handlerType, MethodInfo method) => - typeof(IEventHandler<>).IsGenericInterfaceAssignableFrom(handlerType) && method.Name.Equals(nameof(IEventHandler.HandleAsync)) || - typeof(ISagaEventHandler<>).IsGenericInterfaceAssignableFrom(handlerType) && method.Name.Equals(nameof(ISagaEventHandler.CancelAsync)); -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs deleted file mode 100644 index c68170662..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal.Dispatch; - -internal class SagaDispatcher : DispatcherBase -{ - public SagaDispatcher(IServiceCollection services, Assembly[] assemblies, bool forceInit = false) : base(services, assemblies, forceInit) { } - - public SagaDispatcher Build(ServiceLifetime lifetime) - { - AddSagaDispatchRelation(_services, typeof(IEventHandler<>), lifetime); - AddSagaDispatchRelation(_services, typeof(ISagaEventHandler<>), lifetime); - return this; - } - - private IServiceCollection AddSagaDispatchRelation(IServiceCollection services, Type eventBusHandlerType, ServiceLifetime lifetime) - { - foreach (var item in GetAddSagaServices(eventBusHandlerType)) - { - services.Add(item.ServiceType, item.ImplementationType, lifetime); - AddSagaRelationNetwork(item.ImplementationType); - } - return services; - } - - private void AddSagaRelationNetwork(Type eventBusHandlerType) - { - var eventHandlers = GetSagaHandlers(eventBusHandlerType); - var eventHandler = eventHandlers.Where(x => x.Order != int.MaxValue).FirstOrDefault(); - var actualOrder = eventHandler?.Order ?? int.MaxValue; - if (actualOrder < 0) - { - throw new ArgumentOutOfRangeException("The order must be greater than or equal to 0"); - } - foreach (var handler in eventHandlers) - { - if (actualOrder != handler.Order) - { - handler.Order = actualOrder; - } - AddRelationNetwork(handler.EventType, handler); - } - } - - private List GetSagaHandlers(Type eventBusHandlerType) - { - var methods = eventBusHandlerType.GetMethods(); - Type? eventType = null!; - List eventHandlers = new(); - foreach (var method in methods) - { - var parameters = method.GetParameters(); - if (parameters != null && parameters.Length == 1 && parameters.All(parameter => typeof(IEvent).IsAssignableFrom(parameter.ParameterType) && !typeof(IIntegrationEvent).IsAssignableFrom(parameter.ParameterType)) && IsSagaMode(eventBusHandlerType, method)) - { - var attribute = method.GetCustomAttributes(typeof(EventHandlerAttribute), true).FirstOrDefault(); - var handler = attribute != null ? attribute as EventHandlerAttribute : null; - if (eventType == null) - { - eventType = parameters.Select(x => x.ParameterType).FirstOrDefault()!; - } - - if (handler is null) - { - handler = new EventHandlerAttribute(); - } - handler.ActionMethodInfo = method; - handler.InstanceType = eventBusHandlerType; - handler.EventType = eventType; - handler.IsCancel = method.Name.Equals(nameof(ISagaEventHandler.CancelAsync)); - eventHandlers.Add(handler); - } - } - - //In saga mode, when the user sets an Order for either HandlerAsync or CancelAsync - //the default int.MaxValue Order will be overridden - //but when the Order of HandlerAsync and CancelAsync are inconsistent - //an error will be displayed - if (eventHandlers.Where(handler => handler.Order != int.MaxValue).Select(handler => handler.Order).Distinct().Count() > 1) - { - throw new ArgumentException($"In saga mode {nameof(IEventHandler.HandleAsync)} needs to be the same as {nameof(ISagaEventHandler.CancelAsync)} Order"); - } - return eventHandlers; - } - - private List<(Type ServiceType, Type ImplementationType)> GetAddSagaServices(Type eventBusHandlerType) - { - List<(Type ServiceType, Type ImplementationType)> list = new(); - var serviceTypeAndImplementationInfo = GetSagaServiceTypeAndImplementations(eventBusHandlerType); - foreach (var serviceType in serviceTypeAndImplementationInfo.ServiceTypeList) - { - var implementationTypes = serviceTypeAndImplementationInfo.ImplementationType.Where(implementationType => serviceType.IsAssignableFrom(implementationType)).ToList(); - - foreach (var implementationType in implementationTypes) - { - list.Add((serviceType, implementationType)); - } - } - - return list; - } - - private (List ServiceTypeList, List ImplementationType) GetSagaServiceTypeAndImplementations(Type eventBusHandlerType) - { - var concretions = new List(); - var interfaces = new List(); - foreach (var type in _assemblies.SelectMany(a => a.DefinedTypes).Where(t => !t.IsGeneric())) - { - if (type.IsConcrete()) - { - concretions.Add(type); - } - - if (eventBusHandlerType.IsGenericInterfaceAssignableFrom(type) && !interfaces.Contains(type)) - { - interfaces.AddRange(type.GetInterfaces()); - } - } - return (interfaces, concretions); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs deleted file mode 100644 index 0a5a693ee..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal; - -internal static class DispatcherExtensions -{ - public static IServiceCollection Add(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime) - { - services.Add(new ServiceDescriptor(serviceType, implementationType, lifetime)); - return services; - } - - public static IServiceCollection TryAdd(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime) - { - services.TryAdd(new ServiceDescriptor(serviceType, implementationType, lifetime)); - return services; - } - - public static bool IsGeneric(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition || type.GetTypeInfo().ContainsGenericParameters; - - public static bool IsConcrete(this Type type) => !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; - - public static bool IsGenericInterfaceAssignableFrom(this Type eventHandlerType, Type type) => - type.IsConcrete() && - type.GetInterfaces().Any(t => t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == eventHandlerType); -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs deleted file mode 100644 index 46d3f53e5..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal.Expressions; - -internal delegate Task TaskInvokeDelegate(object target, params object[] parameters); - -internal delegate void VoidInvokeDelegate(object target, object[] parameters); - -internal class InvokeBuilder -{ - public static TaskInvokeDelegate Build(MethodInfo methodInfo, Type targetType) - { - // Parameters to executor - var targetParameter = Expression.Parameter(typeof(object), "target"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // Build parameter list - var parameters = new List(); - var paramInfos = methodInfo.GetParameters(); - for (var i = 0; i < paramInfos.Length; i++) - { - var paramInfo = paramInfos[i]; - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); - var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); - - // valueCast is "(Ti) parameters[i]" - parameters.Add(valueCast); - } - - // Call method - var instanceCast = Expression.Convert(targetParameter, targetType); - var methodCall = Expression.Call(instanceCast, methodInfo, parameters); - - // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" - // Create function - if (methodCall.Type == typeof(void)) - { - var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); - var voidExecutor = lambda.Compile(); - return delegate (object target, object[] parameters) - { - voidExecutor(target, parameters); - return Task.CompletedTask; - }; - } - else if (methodCall.Type == typeof(Task)) - { - // must coerce methodCall to match ActionExecutor signature - var castMethodCall = Expression.Convert(methodCall, typeof(Task)); - var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); - return lambda.Compile(); - } - else - { - throw new NotSupportedException($"The return type of the [{methodInfo.Name}] method must be Task or void"); - } - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs deleted file mode 100644 index a1abbcedf..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Internal.Middleware; - -public class TransactionMiddleware : IMiddleware - where TEvent : notnull, IEvent -{ - private readonly IUnitOfWork? _unitOfWork; - - public TransactionMiddleware(IUnitOfWork? unitOfWork = null) - { - _unitOfWork = unitOfWork; - } - - public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) - { - try - { - await next(); - - if (_unitOfWork is { EntityState: EntityState.Changed }) - { - await _unitOfWork.SaveChangesAsync(); - } - if (IsUseTransaction(@event, out ITransaction? transaction)) - { - await transaction!.UnitOfWork!.CommitAsync(); - } - } - catch (Exception) - { - if (IsUseTransaction(@event, out ITransaction? transaction) && !transaction!.UnitOfWork!.DisableRollbackOnFailure) - { - await transaction.UnitOfWork!.RollbackAsync(); - } - throw; - } - } - - private bool IsUseTransaction(TEvent @event, out ITransaction? transaction) - { - if (@event is ITransaction { UnitOfWork: { UseTransaction: true, TransactionHasBegun: true, CommitState: CommitState.UnCommited } } transactionEvent) - { - transaction = transactionEvent; - return true; - } - - transaction = null; - return false; - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj deleted file mode 100644 index 1e61ad2c1..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/MASA.Contrib.Dispatcher.Events.csproj +++ /dev/null @@ -1,22 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - - - - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs deleted file mode 100644 index 51190ef6b..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Options; - -public class DispatchRelationOptions -{ - public EventHandlerAttribute Handler { get; set; } = new(); - - public IEnumerable CancelHandlers { get; set; } = new List(); - - public DispatchRelationOptions() { } - - public DispatchRelationOptions(EventHandlerAttribute handler) : this() => Handler = handler; - - public void AddCancelHandler(IEnumerable cancelHandlers) - => CancelHandlers = cancelHandlers; - - public bool IsCancelHandler(EventHandlerAttribute cancelHandler) - { - return Handler.FailureLevels == FailureLevels.ThrowAndCancel && cancelHandler.Order <= Handler.Order - || Handler.FailureLevels == FailureLevels.Throw && cancelHandler.Order < Handler.Order; - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs deleted file mode 100644 index 3db90c640..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Options; - -public class DispatcherOptions : IDispatcherOptions -{ - private Assembly[] _assemblies = Array.Empty(); - - public Assembly[] Assemblies - { - get => _assemblies; - set - { - _assemblies = value; - if (_assemblies == null || _assemblies.Length == 0) - { - throw new ArgumentNullException(nameof(_assemblies)); - } - AllEventTypes = _assemblies - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) - .ToList(); - - UnitOfWorkRelation = AllEventTypes.ToDictionary(type => type, IsSupportUnitOfWork); - } - } - - private bool IsSupportUnitOfWork(Type eventType) - => typeof(ITransaction).IsAssignableFrom(eventType) && !typeof(IDomainQuery<>).IsGenericInterfaceAssignableFrom(eventType); - - internal Dictionary UnitOfWorkRelation { get; set; } = new(); - - public IEnumerable AllEventTypes { get; private set; } - - public IServiceCollection Services { get; } - - public DispatcherOptions(IServiceCollection services) => Services = services; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md deleted file mode 100644 index 0d6177dd5..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.md +++ /dev/null @@ -1,194 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## EventBus - -Example๏ผš - -```c# -Install-Package MASA.Contrib.Dispatcher.Events -``` - -##### Basic usage๏ผš - -1. Add EventBus - -```c# -var builder = WebApplication.CreateBuilder(args); -var app = builder.Services - .AddEventBus() - //TODO -``` - -2. Custom Event - -```C# -public class TransferEvent : Event -{ - public string Account { get; set; } = default!; - - public string ReceiveAccount { get; set; } = default!; - - public decimal Money{ get; set; } -} -``` - -3. Send Event - -```C# -IEventBus eventBus;//Get IEventBus through DI -await eventBus.PublishAsync(new TransferEvent());//Send Event -``` - -4. Define Handler - -```C# -public class TransferHandler -{ - [EventHandler] - public Task TransferAsync(TransferEvent @event) - { - //TODO Simulated transfer business - } -} -``` - -Or use the way to implement the interface: - -```C# -public class TransferHandler : IEventHandler -{ - public Task HandleAsync(TransferEvent @event) - { - //TODO Simulated transfer business - } -} -``` - -##### Advanced usage: - -1. Handler arrangement: - -```C# -public class TransferHandler -{ - [EventHandler(1)] - public Task CheckBalanceAsync(TransferEvent @event) - { - //TODO Simulate check balance - } - - [EventHandler(2)] - public Task DeductionBalanceAsync(RegisterUserEvent @event) - { - //TODO Simulated deduction balance - } -} -``` - -2. Support Saga mode - -If there is an error in sending the deducted balance, try again 3 times. If it still fails, check whether the balance is deducted and ensure that there is no deduction and notify the transfer failure - -```C# -public class TransferHandler -{ - [EventHandler(1)] - public Task CheckBalanceAsync(TransferEvent @event) - { - //TODO Simulate check balance - } - - [EventHandler(1, FailureLevels.Ignore, false, true)] - public Task NotificationTransferFailedAsync(TransferEvent @event) - { - //TODO Simulation notification transfer failed - } - - [EventHandler(2, FailureLevels.ThrowAndCancel, true, 3)] - public Task DeductionBalanceAsync(TransferEvent @event) - { - //TODO Simulated deduction balance - throw new Exception("Failed to deduct balance"); - } - - [EventHandler(2, FailureLevels.Ignore, false, true)] - public Task CancelDeductionBalanceAsync(TransferEvent @event) - { - //TODO Idempotent check to ensure that the balance has not been deducted - } -} -``` - -> Execution order: CheckBalanceAsync -> DeductionBalanceAsync (execute 1 time, retry 3 times) -> CancelDeductionBalanceAsync -> NotificationTransferFailedAsync - -Or use the way to implement the interface - -```C# -public class TransferHandler : ISagaEventHandler -{ - [EventHandler(1, FailureLevels.ThrowAndCancel, true, 3)] - public Task HandleAsync(TransferEvent @event) - { - //TODO Simulate check balance deduction balance - } - - [EventHandler(1, FailureLevels.Ignore, false, true)] - public Task CancelAsync(TransferEvent @event) - { - //TODO Idempotent verification and notification of transfer failure - } -} -``` - -> Tip: -> The method where the Handler is located only supports one parameter -> The return type of the method where the Handler is located only supports Task or void two types -> The parameters of the constructor of the class where the Handler is located must support getting from DI - -3. Support Middleware - - 1. Custom Middleware -```C# -public class LoggingMiddleware - : IMiddleware where TEvent : notnull, IEvent -{ - private readonly ILogger> _logger; - public LoggingMiddleware(ILogger> logger) => _logger = logger; - - public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) - { - _logger.LogInformation("----- Handling command {EventName} ({@Event})", typeof(TEvent).FullName, @event); - await next(); - } -} -``` - 2. Enable custom Middleware - - -```C# -builder.Services - .AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)) -``` - -4. Support Transaction - -> Used in conjunction with Contracts.EF and UnitOfWork, when Event implements ITransaction, the transaction will be automatically opened after the first CUD is executed, and the transaction will be submitted after all Handlers are executed. When an exception occurs in the transaction, the transaction will be automatically rolled back. - -##### Summarize - -IEventBus is the core of the event bus. It can be used with CQRS, Uow, MASA.Contrib.DDD.Domain.Repository.EF to automatically execute SaveChange (enable UoW) and Commit (enable UoW without closing transaction) operations after sending Command, And support to roll back the transaction after an exception occurs - -> Question 1. Publishing events through eventBus, Handler error -> and handler throw exception - - > 1. Check custom events or inherited classes to make sure ITransaction is implemented - > 2. Confirm that UoW is used - > 3. Make sure the UseTransaction property of UnitOfWork is false - > 4. Make sure that the DisableRollbackOnFailure property of UnitOfWork is true - -> Question 2. Under what circumstances will SaveChange be automatically saved -> When auto call SaveChange? - - > Use UoW and MASA.Contrib.DDD.Domain.Repository.EF, and use the Add, Update, Delete operations provided by IRepository, publish events through EventBus, and automatically execute SaveChange after executing EventHandler - -> Question 3. If the SaveChange method of UoW is manually called in EventHandler to save, will the framework also save automatically? - - > If the SaveChange method of UoW is manually called in the EventHandler to save, and the Add, Update, and Delete operations provided by IRepository are not used afterward, the SaveChange operation will not be executed twice after the EventHandler execution ends, but if the UoW is manually called. After the SaveChange method is saved and continue to use the Add, Update, and Delete operations provided by IRepository, the framework will call the SaveChange operation again to ensure that the data is saved successfully. \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md deleted file mode 100644 index 271e987d9..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/README.zh-CN.md +++ /dev/null @@ -1,194 +0,0 @@ -ไธญ | [EN](README.md) - -## EventBus - -็”จไพ‹๏ผš - -```c# -Install-Package MASA.Contrib.Dispatcher.Events -``` - -##### ๅŸบๆœฌ็”จๆณ•๏ผš - -1. ๆทปๅŠ EventBus - -```c# -var builder = WebApplication.CreateBuilder(args); -var app = builder.Services - .AddEventBus() - //TODO -``` - -2. ่‡ชๅฎšไน‰Event - -```C# -public class TransferEvent : Event -{ - public string Account { get; set; } = default!; - - public string ReceiveAccount { get; set; } = default!; - - public decimal Money{ get; set; } -} -``` - -3. ๅ‘้€Event - -```C# -IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus -await eventBus.PublishAsync(new TransferEvent());//ๅ‘้€Event -``` - -4. ๅฎšไน‰Handler - -```C# -public class TransferHandler -{ - [EventHandler] - public Task TransferAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿ่ฝฌ่ดฆไธšๅŠก - } -} -``` - -ๆˆ–ไฝฟ็”จๅฎž็ŽฐๆŽฅๅฃ็š„ๆ–นๅผ๏ผš - -```C# -public class TransferHandler : IEventHandler -{ - public Task HandleAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿ่ฝฌ่ดฆไธšๅŠก - } -} -``` - -##### ้ซ˜็บง็”จๆณ•๏ผš - -1. Handler็ผ–ๆŽ’๏ผš - -```C# -public class TransferHandler -{ - [EventHandler(1)] - public Task CheckBalanceAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿๆฃ€ๆŸฅไฝ™้ข - } - - [EventHandler(2)] - public Task DeductionBalanceAsync(RegisterUserEvent @event) - { - //TODO ๆจกๆ‹Ÿๆ‰ฃๅ‡ไฝ™้ข - } -} -``` - -2. ๆ”ฏๆŒSagaๆจกๅผ - -ๅ‡ๅฆ‚ๆ‰ฃๅ‡ไฝ™้ขๅ‘้€ๅ‡บ้”™๏ผŒๅˆ™้‡่ฏ•3ๆฌก๏ผŒๅฆ‚ๆžœไป็„ถๅคฑ่ดฅๅˆ™ๆ ก้ชŒไฝ™้ขๆ˜ฏๅฆๆ‰ฃๅ‡๏ผŒ็กฎไฟๆ— ๆ‰ฃๅ‡ๅŽ้€š็Ÿฅ่ฝฌ่ดฆๅคฑ่ดฅ - -```C# -public class TransferHandler -{ - [EventHandler(1)] - public Task CheckBalanceAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿๆฃ€ๆŸฅไฝ™้ข - } - - [EventHandler(1, FailureLevels.Ignore, false, true)] - public Task NotificationTransferFailedAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿ้€š็Ÿฅ่ฝฌ่ดฆๅคฑ่ดฅ - } - - [EventHandler(2, FailureLevels.ThrowAndCancel, true, 3)] - public Task DeductionBalanceAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿๆ‰ฃๅ‡ไฝ™้ข - throw new Exception("ๆ‰ฃๅ‡ไฝ™้ขๅคฑ่ดฅ"); - } - - [EventHandler(2, FailureLevels.Ignore, false, true)] - public Task CancelDeductionBalanceAsync(TransferEvent @event) - { - //TODO ๅน‚็ญ‰ๆ ก้ชŒ๏ผŒ็กฎไฟไฝ™้ขๆœชๆ‰ฃๅ‡ - } -} -``` - -> ๆ‰ง่กŒ้กบๅบ๏ผš CheckBalanceAsync -> DeductionBalanceAsync ๏ผˆๆ‰ง่กŒ1ๆฌก๏ผŒ้‡่ฏ•3ๆฌก๏ผ‰-> CancelDeductionBalanceAsync -> NotificationTransferFailedAsync - -ๆˆ–่€…ไฝฟ็”จๅฎž็ŽฐๆŽฅๅฃ็š„ๆ–นๅผ - -```C# -public class TransferHandler : ISagaEventHandler -{ - [EventHandler(1, FailureLevels.ThrowAndCancel, true, 3)] - public Task HandleAsync(TransferEvent @event) - { - //TODO ๆจกๆ‹Ÿๆฃ€ๆŸฅไฝ™้ขๆ‰ฃๅ‡ไฝ™้ข - } - - [EventHandler(1, FailureLevels.Ignore, false, true)] - public Task CancelAsync(TransferEvent @event) - { - //TODO ๅน‚็ญ‰ๆ ก้ชŒๅนถ้€š็Ÿฅ่ฝฌ่ดฆๅคฑ่ดฅ - } -} -``` - -> ๆณจๆ„๏ผš -> Handlerๆ‰€ๅœจ็š„ๆ–นๆณ•ไป…ๆ”ฏๆŒไธ€ไธชๅ‚ๆ•ฐ -> Handlerๆ‰€ๅœจ็š„ๆ–นๆณ•่ฟ”ๅ›ž็ฑปๅž‹ไป…ๆ”ฏๆŒTaskๆˆ–voidไธค็ง็ฑปๅž‹ -> Handlerๆ‰€ๅœจ็š„็ฑป็š„ๆž„้€ ๅ‡ฝๆ•ฐ็š„ๅ‚ๆ•ฐๅฟ…้กปๆ”ฏๆŒไปŽDIไธญ่Žทๅ– - -3. ๆ”ฏๆŒMiddleware - - 1. ่‡ชๅฎšไน‰Middleware -```C# -public class LoggingMiddleware - : IMiddleware where TEvent : notnull, IEvent -{ - private readonly ILogger> _logger; - public LoggingMiddleware(ILogger> logger) => _logger = logger; - - public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) - { - _logger.LogInformation("----- Handling command {EventName} ({@Event})", typeof(TEvent).FullName, @event); - await next(); - } -} -``` - 2. ๅฏ็”จ่‡ชๅฎšไน‰Middleware - - -```C# -builder.Services - .AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)) -``` - -4. ๆ”ฏๆŒTransaction - -> ้…ๅˆMASA.Contrib.DDD.Domain.Repository.EF.Repositoryใ€UnitOfWorkไฝฟ็”จ๏ผŒๅฝ“Eventๅฎž็Žฐไบ†ITransaction๏ผŒไผšๅœจๆ‰ง่กŒAddใ€Updateใ€Deleteๆ–นๆณ•ๆ—ถ่‡ชๅŠจๅผ€ๅฏไบ‹ๅŠก๏ผŒไธ”ๅœจHandlerๅ…จ้ƒจๆ‰ง่กŒๅŽๆไบคไบ‹ๅŠก๏ผŒๅฝ“ไบ‹ๅŠกๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒไผš่‡ชๅŠจๅ›žๆปšไบ‹ๅŠก - -##### ๆ€ป็ป“ - -IEventBusๆ˜ฏไบ‹ไปถๆ€ป็บฟ็š„ๆ ธๅฟƒ๏ผŒ้…ๅˆCQRSใ€Uowใ€MASA.Contrib.DDD.Domain.Repository.EFไฝฟ็”จ๏ผŒๅฏๅฎž็Žฐ่‡ชๅŠจๆ‰ง่กŒSaveChange๏ผˆๅฏ็”จUoW๏ผ‰ไธŽCommit๏ผˆๅฏ็”จUoWไธ”ๆ— ๅ…ณ้—ญไบ‹ๅŠก๏ผ‰ๆ“ไฝœ๏ผŒๅนถๆ”ฏๆŒๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒๅ›žๆปšไบ‹ๅŠก - -> ้—ฎ้ข˜1. ้€š่ฟ‡eventBusๅ‘ๅธƒไบ‹ไปถ๏ผŒHandlerๅ‡บ้”™๏ผŒไฝ†ๆ•ฐๆฎไพ็„ถไฟๅญ˜ๅˆฐๆ•ฐๆฎๅบ“ไธญ๏ผŒไบ‹ๅŠกๅนถๆœชๅ›žๆปš - - > 1. ๆฃ€ๆŸฅ่‡ชๅฎšไน‰ไบ‹ไปถๆˆ–็ปงๆ‰ฟ็ฑป๏ผŒ็กฎไฟๅทฒ็ปๅฎž็ŽฐITransaction - > 2. ็กฎ่ฎคๅทฒไฝฟ็”จUoW - > 3. ็กฎ่ฎคUnitOfWork็š„UseTransactionๅฑžๆ€งไธบfalse - > 4. ็กฎ่ฎคUnitOfWork็š„DisableRollbackOnFailureๅฑžๆ€งไธบtrue - -> ้—ฎ้ข˜2. ไป€ไนˆๆ—ถๅ€™่‡ชๅŠจ่ฐƒ็”จSaveChanges - - > ไฝฟ็”จUoWไธ”ไฝฟ็”จไบ†MASA.Contrib.DDD.Domain.Repository.EF๏ผŒๅนถไธ”ไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒ้€š่ฟ‡EventBusๅ‘ๅธƒไบ‹ไปถ๏ผŒๅœจๆ‰ง่กŒEventHandlerๅŽไผš่‡ชๅŠจๆ‰ง่กŒSaveChange - -> ้—ฎ้ข˜3. ๅฆ‚ๆžœๅœจEventHandlerไธญๆ‰‹ๅŠจ่ฐƒ็”จUoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜๏ผŒ้‚ฃๆก†ๆžถ่ฟ˜ไผš่‡ชๅŠจไฟๅญ˜ๅ—๏ผŸ - - > ๅฆ‚ๆžœๅœจEventHandlerไธญๆ‰‹ๅŠจ่ฐƒ็”จไบ†UoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜๏ผŒไธ”ไน‹ๅŽๅนถๆœชๅ†ไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒๅˆ™ๅœจEventHandlerๆ‰ง่กŒ็ป“ๆŸๅŽไธไผšไบŒๆฌกๆ‰ง่กŒSaveChangeๆ“ไฝœ๏ผŒไฝ†ๅฆ‚ๆžœๅœจๆ‰‹ๅŠจ่ฐƒ็”จUoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜ๅŽๅˆ็ปง็ปญไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒๅˆ™ๆก†ๆžถไผšๅ†ๆฌก่ฐƒ็”จSaveChangeๆ“ไฝœไปฅ็กฎไฟๆ•ฐๆฎไฟๅญ˜ๆˆๅŠŸ \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs deleted file mode 100644 index a5f32a733..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddEventBus( - this IServiceCollection services, - Action? options = null) - => services.AddEventBus(ServiceLifetime.Scoped, options); - - public static IServiceCollection AddEventBus( - this IServiceCollection services, - ServiceLifetime lifetime, - Action? options = null) - { - if (services.Any(service => service.ImplementationType == typeof(EventBusProvider))) return services; - services.AddSingleton(); - - DispatcherOptions dispatcherOptions = new DispatcherOptions(services); - options?.Invoke(dispatcherOptions); - if (dispatcherOptions.Assemblies.Length == 0) - { - dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); - } - services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); - - services.AddSingleton(new SagaDispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); - services.AddSingleton(new Internal.Dispatch.Dispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); - services.TryAdd(typeof(IExecutionStrategy), typeof(ExecutionStrategy), ServiceLifetime.Singleton); - services.AddTransient(typeof(IMiddleware<>), typeof(TransactionMiddleware<>)); - services.AddScoped(typeof(IEventBus), typeof(EventBus)); - return services; - } - - public static IServiceCollection AddTestEventBus(this IServiceCollection services, ServiceLifetime lifetime, - Action? options = null) - { - if (services.Any(service => service.ImplementationType == typeof(EventBusProvider))) return services; - services.AddSingleton(); - - DispatcherOptions dispatcherOptions = new DispatcherOptions(services); - options?.Invoke(dispatcherOptions); - if (dispatcherOptions.Assemblies.Length == 0) - { - dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); - } - - services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); - services.AddSingleton(new SagaDispatcher(services, dispatcherOptions.Assemblies, true).Build(lifetime)); - services.AddSingleton(new Internal.Dispatch.Dispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); - services.TryAdd(typeof(IExecutionStrategy), typeof(ExecutionStrategy), ServiceLifetime.Singleton); - services.AddTransient(typeof(IMiddleware<>), typeof(TransactionMiddleware<>)); - services.AddScoped(typeof(IEventBus), typeof(EventBus)); - - return services; - } - - private class EventBusProvider - { - - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs deleted file mode 100644 index a467f31fe..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Strategies; - -public class ExecutionStrategy : IExecutionStrategy -{ - private readonly ILogger? _logger; - - public ExecutionStrategy(ILogger? logger = null) => _logger = logger; - - public async Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @event, Func func, Func cancel) - where TEvent : IEvent - { - int retryTimes = 0; - - Exception exception = null!; - while (strategyOptions.IsRetry(retryTimes)) - { - try - { - if (retryTimes > 0) - { - _logger?.LogWarning("----- Error Publishing event {@Event} start: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); - } - await func.Invoke(@event); - return; - } - catch (Exception ex) - { - if (retryTimes > 0) - { - _logger?.LogWarning("----- Error Publishing event {@Event} finish: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); - } - else - { - _logger?.LogWarning(ex, "----- Error Publishing event {@Event}: after {retries}th executions and we will stop retrying. message id: {messageId} -----", @event, strategyOptions.MaxRetryCount, @event.Id); - } - exception = ex; - retryTimes++; - } - } - - //perform the cancel handler - - await cancel(@event, exception, strategyOptions.FailureLevels); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs deleted file mode 100644 index d1d53e1cf..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Strategies; - -public interface IExecutionStrategy -{ - Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @event, Func func, Func cancel) - where TEvent : IEvent; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs deleted file mode 100644 index b6f879362..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Strategies; - -public class StrategyOptions -{ - /// - /// The maximum number of retry attempts. - /// - public int MaxRetryCount { get; set; } - - public FailureLevels FailureLevels { get; set; } - - public bool IsRetry(int retryTimes) => retryTimes <= MaxRetryCount; - - public void SetStrategy(EventHandlerAttribute dispatchHandler) - { - MaxRetryCount = dispatchHandler.ActualRetryTimes; ; - FailureLevels = dispatchHandler.FailureLevels; - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs deleted file mode 100644 index b3e839e01..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.Events/_Imports.cs +++ /dev/null @@ -1,18 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.DDD.Domain.Events; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.Contrib.Dispatcher.Events.Enums; -global using MASA.Contrib.Dispatcher.Events.Internal; -global using MASA.Contrib.Dispatcher.Events.Internal.Dispatch; -global using MASA.Contrib.Dispatcher.Events.Internal.Expressions; -global using MASA.Contrib.Dispatcher.Events.Internal.Middleware; -global using MASA.Contrib.Dispatcher.Events.Options; -global using MASA.Contrib.Dispatcher.Events.Strategies; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using System.Linq.Expressions; -global using System.Reflection; -global using System.Text.Json.Serialization; diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs deleted file mode 100644 index 016083f40..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public static class DispatcherOptionsExtensions -{ - public static IDispatcherOptions UseDaprEventBus( - this IDispatcherOptions options, - string daprPubsubName = "pubsub", - Action? builder = null) - where TIntegrationEventLogService : class, IIntegrationEventLogService - { - if (options.Services == null) - { - throw new ArgumentNullException(nameof(options.Services)); - } - - options.Services.TryAddDaprEventBus(builder, dispatcherOptions => - { - dispatcherOptions.PubSubName = daprPubsubName; - }); - return options; - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs deleted file mode 100644 index b2eb56cff..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs +++ /dev/null @@ -1,6 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public interface IProcessingServer -{ - Task ExecuteAsync(CancellationToken stoppingToken); -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs deleted file mode 100644 index e33755dde..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs +++ /dev/null @@ -1,13 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public interface IProcessor -{ - Task ExecuteAsync(CancellationToken stoppingToken); - - /// - /// Easy to switch between background tasks - /// - /// unit: seconds - /// - Task DelayAsync(int delay); -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs deleted file mode 100644 index b0378e427..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public abstract record IntegrationEvent(Guid Id, DateTime CreationTime) : IIntegrationEvent -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - [JsonIgnore] - public IUnitOfWork? UnitOfWork { get; set; } - - [JsonIgnore] - public abstract string Topic { get; set; } - - public IntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs deleted file mode 100644 index 23aa53e9c..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public class IntegrationEventBus : IIntegrationEventBus -{ - private readonly DispatcherOptions _dispatcherOptions; - private readonly DaprClient _dapr; - private readonly ILogger? _logger; - private readonly IIntegrationEventLogService _eventLogService; - private readonly IOptionsMonitor? _appConfig; - private readonly string _daprPubsubName; - private readonly IEventBus? _eventBus; - private readonly IUnitOfWork? _unitOfWork; - - public IntegrationEventBus(IOptions options, - DaprClient dapr, - IIntegrationEventLogService eventLogService, - IOptionsMonitor? appConfig = null, - ILogger? logger = null, - IEventBus? eventBus = null, - IUnitOfWork? unitOfWork = null) - { - _dispatcherOptions = options.Value; - _dapr = dapr; - _eventLogService = eventLogService; - _appConfig = appConfig; - _logger = logger; - _daprPubsubName = options.Value.PubSubName; - _eventBus = eventBus; - _unitOfWork = unitOfWork; - } - - public IEnumerable GetAllEventTypes() => - _eventBus == null - ? _dispatcherOptions.AllEventTypes - : _dispatcherOptions.AllEventTypes.Concat(_eventBus.GetAllEventTypes()).Distinct(); - - public async Task PublishAsync(TEvent @event) - where TEvent : IEvent - { - if (@event is IIntegrationEvent integrationEvent) - { - await PublishIntegrationAsync(integrationEvent); - } - else if (_eventBus != null) - { - await _eventBus.PublishAsync(@event); - } - else - { - throw new NotSupportedException(nameof(@event)); - } - } - - private async Task PublishIntegrationAsync(TEvent @event) - where TEvent : IIntegrationEvent - { - if (@event.UnitOfWork == null && _unitOfWork != null) - @event.UnitOfWork = _unitOfWork; - - var topicName = @event.Topic; - if (@event.UnitOfWork != null && !@event.UnitOfWork.UseTransaction) - { - try - { - _logger?.LogDebug("----- Saving changes and integrationEvent: {IntegrationEventId}", @event.Id); - await _eventLogService.SaveEventAsync(@event, @event.UnitOfWork!.Transaction); - - _logger?.LogDebug( - "----- Publishing integration event: {IntegrationEventIdPublished} from {AppId} - ({IntegrationEvent})", @event.Id, - _appConfig?.CurrentValue.AppId ?? string.Empty, @event); - - await _eventLogService.MarkEventAsInProgressAsync(@event.Id); - - _logger?.LogDebug("Publishing event {Event} to {PubsubName}.{TopicName}", @event, _daprPubsubName, topicName); - await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); - - await _eventLogService.MarkEventAsPublishedAsync(@event.Id); - } - catch (Exception ex) - { - _logger?.LogError(ex, "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", - @event.Id, _appConfig?.CurrentValue.AppId ?? string.Empty, @event); - LocalQueueProcessor.Default.AddJobs(new IntegrationEventLogItem(@event.Id, @event.Topic, @event)); - await _eventLogService.MarkEventAsFailedAsync(@event.Id); - } - } - else - { - await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); - } - } - - public async Task CommitAsync(CancellationToken cancellationToken = default) - { - if (_unitOfWork is null) - throw new ArgumentNullException(nameof(IUnitOfWork), "You need to UseUoW when adding services"); - - await _unitOfWork.CommitAsync(cancellationToken); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs deleted file mode 100644 index 66a0074c8..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs +++ /dev/null @@ -1,20 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public class IntegrationEventHostedService : BackgroundService -{ - private readonly ILogger? _logger; - private readonly IProcessingServer _processingServer; - - public IntegrationEventHostedService(IProcessingServer processingServer, ILogger? logger) - { - _logger = logger; - _processingServer = processingServer; - } - - protected override Task ExecuteAsync(CancellationToken stoppingToken) - { - _logger?.LogDebug("----- IntegrationEvent background task is starting"); - - return _processingServer.ExecuteAsync(stoppingToken); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs deleted file mode 100644 index 2875d237e..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs +++ /dev/null @@ -1,31 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; - -/// -/// Use the local queue to retry sending failed messages -/// -internal class IntegrationEventLogItem -{ - public Guid EventId { get; } - - public string Topic { get; } - - public DateTime CreationTime { get; } - - public int RetryCount { get; private set; } - - public object Event { get; } - - public IntegrationEventLogItem(Guid eventId, string topic, object @event) - { - EventId = eventId; - Topic = topic; - RetryCount = 0; - CreationTime = DateTime.UtcNow; - Event = @event; - } - - public void Retry() - { - this.RetryCount++; - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs deleted file mode 100644 index 3ffe46da8..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; - -internal class LocalQueueProcessor -{ - private readonly ConcurrentDictionary _retryEventLogs; - - public static ILogger? Logger; - public static readonly LocalQueueProcessor Default = new(); - - public LocalQueueProcessor() => _retryEventLogs = new(); - - public static void SetLogger(IServiceCollection services) - { - Logger = services.BuildServiceProvider().GetService>(); - } - - public void AddJobs(IntegrationEventLogItem items) - => _retryEventLogs.TryAdd(items.EventId, items); - - public void RemoveJobs(Guid eventId) - => _retryEventLogs.TryRemove(eventId, out _); - - public void RetryJobs(Guid eventId) - { - if (_retryEventLogs.TryGetValue(eventId, out IntegrationEventLogItem? item)) - { - item.Retry(); - } - } - - public bool IsExist(Guid eventId) - => _retryEventLogs.ContainsKey(eventId); - - public void Delete(int maxRetryTimes) - { - var eventLogItems = _retryEventLogs.Values.Where(log => log.RetryCount >= maxRetryTimes - 1).ToList(); - eventLogItems.ForEach(item => RemoveJobs(item.EventId)); - } - - public List RetrieveEventLogsFailedToPublishAsync(int maxRetryTimes, int retryBatchSize) - { - try - { - return _retryEventLogs - .Select(item => item.Value) - .Where(log => log.RetryCount < maxRetryTimes) - .OrderBy(log => log.RetryCount) - .ThenBy(log => log.CreationTime) - .Take(retryBatchSize) - .ToList(); - } - catch (Exception ex) - { - Logger?.LogWarning(ex, "... getting local retry queue error"); - - Thread.Sleep(TimeSpan.FromSeconds(2)); - return new List(); - } - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj deleted file mode 100644 index 5abe0bb1f..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj +++ /dev/null @@ -1,21 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - - - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs deleted file mode 100644 index f5905da71..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs +++ /dev/null @@ -1,158 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; - -public class DispatcherOptions : IDispatcherOptions -{ - private string _pubSubName = "pubsub"; - - public string PubSubName - { - get => _pubSubName; - set - { - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentNullException(nameof(_pubSubName)); - } - - _pubSubName = value; - } - } - - /// - /// Local queue maximum number of retries - /// - public int LocalRetryTimes { get; set; } = 3; - - /// - /// maximum number of retries - /// Default is 10 - /// - public int MaxRetryTimes { get; set; } = 10; - - private int _failedRetryInterval = 60; - - /// - /// The interval at which db polls for failure messages. - /// Default is 60 seconds. - /// unit: seconds - /// - public int FailedRetryInterval - { - get => _failedRetryInterval; - set - { - if (value <= 0) - throw new ArgumentException("must be greater than or equal to 0", nameof(FailedRetryInterval)); - - _failedRetryInterval = value; - } - } - - /// - /// Minimum execution retry interval - /// Default is 60 seconds. - /// - public int MinimumRetryInterval { get; set; } = 60; - - private int _localFailedRetryInterval = 3; - - /// - /// The interval at which the local queue is polled for failed messages. - /// Local queue does not rebuild after service crash - /// Default is 3 seconds. - /// unit: seconds - /// - public int LocalFailedRetryInterval - { - get => _localFailedRetryInterval; - set - { - if (value <= 0) - throw new ArgumentException("must be greater than or equal to 0", nameof(LocalFailedRetryInterval)); - - _localFailedRetryInterval = value; - } - } - - /// - /// maximum number of retries per retry - /// - public int RetryBatchSize { get; set; } = 100; - - private int _cleaningLocalQueueExpireInterval = 60; - - /// - /// Delete local queue expired event interval - /// Default is 60 seconds - /// unit: seconds - /// - public int CleaningLocalQueueExpireInterval - { - get => _cleaningLocalQueueExpireInterval; - set - { - if (value <= 0) - throw new ArgumentException("must be greater than or equal to 0", nameof(CleaningLocalQueueExpireInterval)); - - _cleaningLocalQueueExpireInterval = value; - } - } - - private int _cleaningExpireInterval = 300; - - /// - /// Delete expired event interval - /// Default is 300 seconds. - /// unit: seconds - /// - public int CleaningExpireInterval - { - get => _cleaningExpireInterval; - set - { - if (value <= 0) - throw new ArgumentException("must be greater than or equal to 0", nameof(CleaningExpireInterval)); - - _cleaningExpireInterval = value; - } - } - - /// - /// Expiration time, when the message status is successful and has expired, it will be deleted by the scheduled task - /// Default: ( 24 * 3600 )s - /// - public long PublishedExpireTime { get; set; } = 24 * 3600; - - /// - /// Bulk delete expired messages - /// - public int DeleteBatchCount { get; set; } = 1000; - - public Func? GetCurrentTime { get; set; } = null; - - public IServiceCollection Services { get; } - - private Assembly[] _assemblies = Array.Empty(); - - public Assembly[] Assemblies - { - get => _assemblies; - set - { - _assemblies = value; - if (_assemblies == null || _assemblies.Length == 0) - { - throw new ArgumentNullException(nameof(_assemblies)); - } - - AllEventTypes = _assemblies - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) - .ToList(); - } - } - - public List AllEventTypes { get; private set; } - - public DispatcherOptions(IServiceCollection services) => Services = services; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs deleted file mode 100644 index f3349ce48..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs +++ /dev/null @@ -1,24 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; - -public class DeleteLocalQueueExpiresProcessor : ProcessorBase -{ - private readonly IOptions _options; - - public DeleteLocalQueueExpiresProcessor(IOptions options) - { - _options = options; - } - - /// - /// Delete expired events - /// - /// - /// - public override Task ExecuteAsync(CancellationToken stoppingToken) - { - LocalQueueProcessor.Default.Delete(_options.Value.LocalRetryTimes); - return Task.CompletedTask; - } - - public override int Delay => _options.Value.CleaningLocalQueueExpireInterval; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs deleted file mode 100644 index cd6d24cf8..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs +++ /dev/null @@ -1,32 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; - -public class DeletePublishedExpireEventProcessor : ProcessorBase -{ - private readonly IServiceProvider _serviceProvider; - private readonly IOptions _options; - - public DeletePublishedExpireEventProcessor( - IServiceProvider serviceProvider, - IOptions options) - { - _serviceProvider = serviceProvider; - _options = options; - } - - /// - /// Delete expired events - /// - /// - /// - public override async Task ExecuteAsync(CancellationToken stoppingToken) - { - using (var scope = _serviceProvider.CreateScope()) - { - var logService = scope.ServiceProvider.GetRequiredService(); - var expireDate = (_options.Value.GetCurrentTime?.Invoke() ?? DateTime.UtcNow).AddSeconds(-_options.Value.PublishedExpireTime); - await logService.DeleteExpiresAsync(expireDate, _options.Value.DeleteBatchCount, stoppingToken); - } - } - - public override int Delay => _options.Value.CleaningExpireInterval; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs deleted file mode 100644 index fd032471e..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs +++ /dev/null @@ -1,35 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; - -public class InfiniteLoopProcessor : ProcessorBase -{ - private readonly IProcessor _processor; - private readonly ILogger? _logger; - - public InfiniteLoopProcessor(IProcessor processor, ILogger? logger = null) - { - _processor = processor; - _logger = logger; - } - - public override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - try - { - await _processor.ExecuteAsync(stoppingToken); - await DelayAsync(((ProcessorBase)_processor).Delay); - } - catch (OperationCanceledException) - { - //ignore - } - catch (Exception ex) - { - _logger?.LogWarning(ex, "Processor '{ProcessorName}' failed", _processor.ToString()); - - Thread.Sleep(TimeSpan.FromSeconds(2)); - } - } - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs deleted file mode 100644 index 2529ada89..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs +++ /dev/null @@ -1,19 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; - -public abstract class ProcessorBase : IProcessor -{ - public abstract Task ExecuteAsync(CancellationToken stoppingToken); - - // /// - // /// Easy to switch between background tasks - // /// - /// unit: seconds - // /// - public Task DelayAsync(int delay) - => Task.Delay(TimeSpan.FromSeconds(delay)); - - /// - /// Task delay time, unit: seconds - /// - public virtual int Delay { get; } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs deleted file mode 100644 index fafb1dcb2..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs +++ /dev/null @@ -1,76 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; - -public class RetryByDataProcessor : ProcessorBase -{ - private readonly IServiceProvider _serviceProvider; - private readonly IOptions _options; - private readonly IOptionsMonitor _appConfig; - private readonly ILogger? _logger; - - public RetryByDataProcessor( - IServiceProvider serviceProvider, - IOptionsMonitor appConfig, - IOptions options, - ILogger? logger = null) - { - _serviceProvider = serviceProvider; - _appConfig = appConfig; - _options = options; - _logger = logger; - } - - public override async Task ExecuteAsync(CancellationToken stoppingToken) - { - using (var scope = _serviceProvider.CreateScope()) - { - var unitOfWork = scope.ServiceProvider.GetService(); - if (unitOfWork != null) - unitOfWork.UseTransaction = false; - - var dapr = _serviceProvider.GetRequiredService(); - var eventLogService = scope.ServiceProvider.GetRequiredService(); - - var retrieveEventLogs = - await eventLogService.RetrieveEventLogsFailedToPublishAsync(_options.Value.RetryBatchSize, _options.Value.MaxRetryTimes, _options.Value.MinimumRetryInterval); - - foreach (var eventLog in retrieveEventLogs) - { - try - { - if (LocalQueueProcessor.Default.IsExist(eventLog.EventId)) - continue; // The local queue is retrying, no need to retry - - await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId); - - _logger?.LogDebug("Publishing integration event {Event} to {PubsubName}.{TopicName}", eventLog, - _options.Value.PubSubName, - eventLog.Event.Topic); - - await dapr.PublishEventAsync(_options.Value.PubSubName, eventLog.Event.Topic, eventLog.Event, stoppingToken); - - LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); - - await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId); - } - catch (UserFriendlyException) - { - //Update state due to multitasking contention, no processing required - } - catch (Exception ex) - { - _logger?.LogError(ex, - "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", - eventLog.EventId, _appConfig.CurrentValue.AppId, eventLog); - await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); - } - finally - { - if (unitOfWork != null && unitOfWork.TransactionHasBegun) - await unitOfWork.CommitAsync(stoppingToken); - } - } - } - } - - public override int Delay => _options.Value.FailedRetryInterval; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs deleted file mode 100644 index 452cb9b33..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; - -public class RetryByLocalQueueProcessor : ProcessorBase -{ - private readonly IServiceProvider _serviceProvider; - private readonly IOptionsMonitor _appConfig; - private readonly IOptions _options; - private readonly ILogger? _logger; - - public RetryByLocalQueueProcessor( - IServiceProvider serviceProvider, - IOptionsMonitor appConfig, - IOptions options, - ILogger? logger = null) - { - _serviceProvider = serviceProvider; - _appConfig = appConfig; - _options = options; - _logger = logger; - } - - public override async Task ExecuteAsync(CancellationToken stoppingToken) - { - using (var scope = _serviceProvider.CreateScope()) - { - var unitOfWork = scope.ServiceProvider.GetService(); - if (unitOfWork != null) - unitOfWork.UseTransaction = false; - - var dapr = _serviceProvider.GetRequiredService(); - var eventLogService = scope.ServiceProvider.GetRequiredService(); - - var retrieveEventLogs = LocalQueueProcessor.Default.RetrieveEventLogsFailedToPublishAsync(_options.Value.LocalRetryTimes, _options.Value.RetryBatchSize); - - foreach (var eventLog in retrieveEventLogs) - { - try - { - LocalQueueProcessor.Default.RetryJobs(eventLog.EventId); - - await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId); - - _logger?.LogDebug( - "Publishing integration event {Event} to {PubsubName}.{TopicName}", - eventLog, - _options.Value.PubSubName, - eventLog.Topic); - - await dapr.PublishEventAsync(_options.Value.PubSubName, eventLog.Topic, eventLog.Event, stoppingToken); - - await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId); - - LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); - } - catch (UserFriendlyException) - { - //Update state due to multitasking contention - LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); - } - catch (Exception ex) - { - _logger?.LogError(ex, - "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", - eventLog.EventId, _appConfig.CurrentValue.AppId, eventLog); - await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); - } - } - } - } - - public override int Delay => _options.Value.LocalFailedRetryInterval; -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md deleted file mode 100644 index 4b2017a7b..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md +++ /dev/null @@ -1,104 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## IntegrationEventBus - -Example: - -```C# -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr //Send cross-process messages -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //Record cross-process message logs -Install-Package MASA.Contrib.Data.UoW.EF //Use UnitOfWork -``` - -1. Add IIntegrationEventBus - -```C# -builder.Services - .AddDaprEventBus(options=> - { - options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity")) - .UseEventLog(); - ) - }); -``` - -> CustomerDbContext needs to inherit IntegrationEventLogContext - -2. Custom IntegrationEvent - -```C# -public class DemoIntegrationEvent : IntegrationEvent -{ - public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name - - //todo Custom attribute parameters -} -``` - -3. Custom CustomDbContext - -```C# -public class CustomDbContext : IntegrationEventLogContext -{ - public DbSet Users { get; set; } = null!; - - public CustomDbContext(MasaDbContextOptions options) : base(options) - { - - } -} -``` - -4. Send Event - -```C# -IIntegrationEventBus eventBus;//Get IIntegrationEventBus through DI -await eventBus.PublishAsync(new DemoIntegrationEvent());//Send cross-process events -``` - -5. Subscribe to events - -```C# -[Topic("pubsub", nameof(DomeIntegrationEvent))] -public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) -{ - //todo -} -``` - -### retry policy - -```C# -builder.Services - .AddDaprEventBus(options=> - { - // options.MaxRetryTimes = 50;//Maximum number of retries, default: 50 - // options.RetryBatchSize = 100;//Number of single retry events, used to get retry events from persistent data source, default 100 - // options.FailedRetryInterval = 60;//Persistent data source retry pause interval, default 60s - // options.CleaningExpireInterval = 300;//Clearing expired event pause interval, unit: s, default 300s - // options.ExpireDate = 24 * 3600;//Expiration time, CreationTime + ExpireDate = Expiration time, default 1 day - - // options.LocalFailedRetryInterval = 3;//Local queue retry pause interval, default 3s - // options.CleaningLocalQueueExpireInterval = 60;//Clearing local queue expired event pause interval, unit: s, default 60s - }); -``` - -Retry is divided into local queue retry and retry from persistent data source: - -local queue: - -Features: -- Short retry interval, support second-level retry interval -- Get data from memory, faster -- After the system crashes, the previous local queue will not be rebuilt, and will be automatically demoted to the persistent queue to retry the task - -Persistent data source queue: - -Features: - -- After the system crashes, the retry queue can be obtained from db or other persistent sources to ensure 100% retry of events -- As a downgrade solution for local memory queues, lower pressure on db or other data sources - -In the case of a single copy, the tasks of the two queues will only be executed in a single queue, and there will be no simultaneous execution of the two queues. -In the case of multiple copies, the same task may be executed by multiple copies. Although we have made idempotent, but the delivery guarantee is At Least Once, it is still possible that the event publishing is successful, but the state change fails. -At this point, the event may be re-sent. We recommend that the task executor retry across events. \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md deleted file mode 100644 index af9d84520..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md +++ /dev/null @@ -1,105 +0,0 @@ -ไธญ | [EN](README.md) - -## IntegrationEventBus - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.Dapr //ๅ‘้€่ทจ่ฟ›็จ‹ๆถˆๆฏ -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //่ฎฐๅฝ•่ทจ่ฟ›็จ‹ๆถˆๆฏๆ—ฅๅฟ— -Install-Package MASA.Contrib.Data.UoW.EF //ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ -``` - -1. ๆทปๅŠ IIntegrationEventBus - -```C# -builder.Services - .AddDaprEventBus(options=> - { - options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"))//ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ๏ผŒๆŽจ่ไฝฟ็”จ - .UseEventLog(); - ) - }); -``` - -> CustomerDbContext ้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext - -2. ่‡ชๅฎšไน‰ IntegrationEvent - -```C# -public class DemoIntegrationEvent : IntegrationEvent -{ - public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name - - //todo ่‡ชๅฎšไน‰ๅฑžๆ€งๅ‚ๆ•ฐ -} -``` - -3. ่‡ชๅฎšไน‰CustomDbContext - -```C# -public class CustomDbContext : IntegrationEventLogContext -{ - public DbSet Users { get; set; } = null!; - - public CustomDbContext(MasaDbContextOptions options) : base(options) - { - - } -} -``` - -4. ๅ‘้€ Event - -```C# -IIntegrationEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIIntegrationEventBus -await eventBus.PublishAsync(new DemoIntegrationEvent());//ๅ‘้€่ทจ่ฟ›็จ‹ไบ‹ไปถ -``` - -5. ่ฎข้˜…ไบ‹ไปถ - -```C# -[Topic("pubsub", nameof(DomeIntegrationEvent))] -public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) -{ - //todo -} -``` - -### ้‡่ฏ•็ญ–็•ฅ - -```C# -builder.Services - .AddDaprEventBus(options=> - { - // options.MaxRetryTimes = 50;//ๆœ€ๅคง้‡่ฏ•ๆฌกๆ•ฐ, ้ป˜่ฎค๏ผš50 - // options.RetryBatchSize = 100;//ๅ•ๆฌก้‡่ฏ•ไบ‹ไปถๆ•ฐ้‡, ็”จไบŽไปŽๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ่Žทๅ–ๅพ…้‡่ฏ•ไบ‹ไปถ, ้ป˜่ฎค100 - // options.FailedRetryInterval = 60;//ๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้‡่ฏ•ๅœๆญ‡้—ด้š”, ้ป˜่ฎค60s - // options.CleaningExpireInterval = 300;//ๆธ…้™คๅทฒ่ฟ‡ๆœŸไบ‹ไปถๅœๆญ‡้—ด้š”๏ผŒๅ•ไฝ๏ผšs, ้ป˜่ฎค 300s - // options.ExpireDate = 24 * 3600;//่ฟ‡ๆœŸๆ—ถ้—ด๏ผŒCreationTime + ExpireDate = ่ฟ‡ๆœŸๆ—ถ้—ด, ้ป˜่ฎค1ๅคฉ - - // options.LocalFailedRetryInterval = 3;//ๆœฌๅœฐ้˜Ÿๅˆ—้‡่ฏ•ๅœๆญ‡้—ด้š”, ้ป˜่ฎค3s - // options.CleaningLocalQueueExpireInterval = 60;//ๆธ…้™คๆœฌๅœฐ้˜Ÿๅˆ—ๅทฒ่ฟ‡ๆœŸไบ‹ไปถๅœๆญ‡้—ด้š”๏ผŒๅ•ไฝ๏ผšs, ้ป˜่ฎค 60s - }); -``` - -้‡่ฏ•ๅˆ†ไธบๆœฌๅœฐ้˜Ÿๅˆ—้‡่ฏ•ไปฅๅŠไปŽๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้‡่ฏ•ไธค็ง๏ผš - -ๆœฌๅœฐ้˜Ÿๅˆ—๏ผš - -็‰น็‚น๏ผš -- ้‡่ฏ•้—ด้š”็Ÿญ๏ผŒๆ”ฏๆŒ็ง’็บงๅˆซ้‡่ฏ•้—ด้š” -- ไปŽๅ†…ๅญ˜่Žทๅ–ๆ•ฐๆฎ๏ผŒ้€Ÿๅบฆๆ›ดๅฟซ -- ็ณป็ปŸๅดฉๆบƒๅŽ๏ผŒไน‹ๅ‰็š„ๆœฌๅœฐ้˜Ÿๅˆ—ไธไผš้‡ๅปบ๏ผŒ่‡ชๅŠจ้™็บงๅˆฐๆŒไน…ๅŒ–้˜Ÿๅˆ—ไธญ้‡่ฏ•ไปปๅŠก - -ๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้˜Ÿๅˆ—๏ผš - -็‰น็‚น๏ผš - -- ็ณป็ปŸๅดฉๆบƒๅŽ๏ผŒๅฏไปฅไปŽdbๆˆ–่€…ๅ…ถไป–ๆŒไน…ๅŒ–ๆบ่Žทๅ–้‡่ฏ•้˜Ÿๅˆ—๏ผŒ็กฎไฟไบ‹ไปถ100%้‡่ฏ• -- ไฝœไธบๆœฌๅœฐๅ†…ๅญ˜้˜Ÿๅˆ—็š„้™็บงๆ–นๆกˆ๏ผŒๅฏนdbๆˆ–่€…ๅ…ถไป–ๆ•ฐๆฎๆบๅŽ‹ๅŠ›ๆ›ดไฝŽ - -ๅœจๅ•ๅ‰ฏๆœฌๆƒ…ๅ†ตไธ‹๏ผŒไธค็ง้˜Ÿๅˆ—็š„ไปปๅŠกไป…ไผšๅœจๅ•ไธช้˜Ÿๅˆ—ไธญๆ‰ง่กŒ๏ผŒไธไผšๅญ˜ๅœจไธคไธช้˜Ÿๅˆ—ๅŒๆ—ถๆ‰ง่กŒ็š„ๆƒ…ๅ†ตใ€‚ -ๅœจๅคšๅ‰ฏๆœฌๆƒ…ๅ†ตไธ‹๏ผŒๅŒไธ€ไธชไปปๅŠกๅฏ่ƒฝไผš่ขซๅคšไธชๅ‰ฏๆœฌๆ‰€ๆ‰ง่กŒ๏ผŒ่™ฝ็„ถๆˆ‘ไปฌๆœ‰ๅšๅน‚็ญ‰๏ผŒไฝ†ไธบไบคไป˜ไฟ่ฏๆ˜ฏ At Least Once๏ผŒไป็„ถๆœ‰ๅฏ่ƒฝๅ‡บ็Žฐไบ‹ไปถๅ‘ๅธƒๆˆๅŠŸ๏ผŒไฝ†็Šถๆ€ๆ›ดๆ”นๅคฑ่ดฅ็š„ๆƒ…ๅ†ต๏ผŒ -ๆญคๆ—ถไบ‹ไปถๅฏ่ƒฝไผš้‡ๅ‘๏ผŒๆˆ‘ไปฌๅปบ่ฎฎไปปๅŠกๆ‰ง่กŒ่€…ๅšๅฅฝๅฏน่ทจไบ‹ไปถ็š„้‡่ฏ• - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs deleted file mode 100644 index 20a92ec81..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs +++ /dev/null @@ -1,20 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; - -public class DefaultHostedService : IProcessingServer -{ - private readonly IEnumerable _processors; - private readonly ILogger? _logger; - - public DefaultHostedService(IEnumerable processors, ILogger? logger = null) - { - _processors = processors; - _logger = logger; - } - - public Task ExecuteAsync(CancellationToken stoppingToken) - { - var processorTasks = _processors.Select(processor => new InfiniteLoopProcessor(processor, _logger)) - .Select(process => process.ExecuteAsync(stoppingToken)); - return Task.WhenAll(processorTasks); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs deleted file mode 100644 index 5281efc2c..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddDaprEventBus( - this IServiceCollection services, - Action? options = null) - where TIntegrationEventLogService : class, IIntegrationEventLogService - => services.TryAddDaprEventBus(null, options); - - internal static IServiceCollection TryAddDaprEventBus( - this IServiceCollection services, - Action? builder, - Action? options = null) - where TIntegrationEventLogService : class, IIntegrationEventLogService - { - if (services.Any(service => service.ImplementationType == typeof(IntegrationEventBusProvider))) - return services; - - services.AddSingleton(); - - var dispatcherOptions = new DispatcherOptions(services); - options?.Invoke(dispatcherOptions); - - if (dispatcherOptions.Assemblies.Length == 0) - dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); - - services.TryAddSingleton(typeof(IOptions), - serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); - - LocalQueueProcessor.SetLogger(services); - services.AddDaprClient(builder); - services.AddScoped(); - services.AddScoped(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.TryAddSingleton(); - services.AddHostedService(); - if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) - { - var logger = services.BuildServiceProvider().GetService>(); - logger?.LogWarning("UoW is not enabled, local messages will not be integrated"); - } - - return services; - } - - private class IntegrationEventBusProvider - { - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs deleted file mode 100644 index 1c635a31f..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs +++ /dev/null @@ -1,19 +0,0 @@ -global using Dapr.Client; -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; -global using MASA.Utils.Models.Config; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Hosting; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using System.Collections.Concurrent; -global using System.Reflection; -global using System.Text.Json.Serialization; - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs deleted file mode 100644 index 7fe638666..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF; - -public static class DispatcherOptionsExtensions -{ - /// - /// IntegrationEventLogContext is a separate database - /// - /// - /// Separately specify database configuration for IntegrationEventLogContext - /// - /// - public static IDispatcherOptions UseEventLog( - this IDispatcherOptions options, - Action optionsBuilder) - { - if (options.Services == null) - throw new ArgumentNullException(nameof(options.Services)); - - if (optionsBuilder == null) - throw new ArgumentNullException(nameof(optionsBuilder)); - - if (options.Services.Any(service => service.ImplementationType == typeof(EventLogProvider))) return options; - options.Services.AddSingleton(); - - options.Services.AddCustomMasaDbContext(optionsBuilder); - return options; - } - - /// - /// User database with IntegrationEventLogContext merge - /// User-defined DbContext need IntegrationEventLogContext inheritance - /// - /// - /// - /// - public static IDispatcherOptions UseEventLog( - this IDispatcherOptions options) where TDbContext : IntegrationEventLogContext - { - if (options.Services == null) - throw new ArgumentNullException(nameof(options.Services)); - - if (typeof(TDbContext) == typeof(IntegrationEventLogContext)) - throw new NotSupportedException( - $"{typeof(TDbContext).FullName} must be IntegrationEventLogContext derived classes, or using UseEventLog() replace UseEventLog<{typeof(TDbContext).FullName}>()"); - - if (options.Services.Any(service => service.ImplementationType == typeof(EventLogProvider))) return options; - options.Services.AddSingleton(); - - options.Services.TryAddScoped(serviceProvider => serviceProvider.GetRequiredService()); - return options; - } - - private class EventLogProvider - { - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs deleted file mode 100644 index 83f53bcf5..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF; - -public class IntegrationEventLogContext : MasaDbContext -{ - public IntegrationEventLogContext( - MasaDbContextOptions? options = null, - MasaDbContextOptions? eventLogContext = null) - : base(eventLogContext ?? options ?? - throw new InvalidOperationException("Options extension of type 'CoreOptionsExtension' not found")) - { - } - - public DbSet EventLogs { get; set; } - - protected override void OnModelCreatingExecuting(ModelBuilder builder) - { - builder.Entity(ConfigureEventLogEntry); - } - - private void ConfigureEventLogEntry(EntityTypeBuilder builder) - { - builder.ToTable("IntegrationEventLog"); - - builder.HasKey(e => e.Id); - - builder.Property(e => e.Id) - .IsRequired(); - - builder.Property(e => e.Content) - .IsRequired(); - - builder.Property(e => e.CreationTime) - .IsRequired(); - - builder.Property(e => e.ModificationTime) - .IsRequired(); - - builder.Property(e => e.State) - .IsRequired(); - - builder.Property(e => e.TimesSent) - .IsRequired(); - - builder.Property(e => e.RowVersion) - .IsRowVersion(); - - builder.Property(e => e.EventTypeName) - .IsRequired(); - - builder.HasIndex(e => new { e.State, e.ModificationTime },"index_state_modificationtime"); - builder.HasIndex(e => new { e.State, e.TimesSent, e.ModificationTime },"index_state_timessent_modificationtime"); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs deleted file mode 100644 index 29a04682f..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs +++ /dev/null @@ -1,158 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF; - -public class IntegrationEventLogService : IIntegrationEventLogService -{ - private readonly IntegrationEventLogContext _eventLogContext; - private readonly IServiceProvider _serviceProvider; - private readonly Logger? _logger; - private IEnumerable? _eventTypes; - - public IntegrationEventLogService( - IntegrationEventLogContext eventLogContext, - IServiceProvider serviceProvider, - Logger? logger = null) - { - _eventLogContext = eventLogContext; - _serviceProvider = serviceProvider; - _logger = logger; - } - - /// - /// Get messages to retry - /// - /// maximum number of retries per retry - /// - /// default: 60s - /// - public async Task> RetrieveEventLogsFailedToPublishAsync(int retryBatchSize = 200, int maxRetryTimes = 10, int minimumRetryInterval = 60) - { - //todo: Subsequent acquisition of the current time needs to be uniformly replaced with the unified time method provided by the framework, which is convenient for subsequent uniform replacement to UTC time or other urban time. The default setting here is Utc time. - var time = DateTime.UtcNow.AddSeconds(-minimumRetryInterval); - var result = await _eventLogContext.EventLogs - .Where(e => (e.State == IntegrationEventStates.PublishedFailed || e.State == IntegrationEventStates.InProgress) && - e.TimesSent <= maxRetryTimes && - e.ModificationTime < time) - .OrderBy(o => o.CreationTime) - .Take(retryBatchSize) - .ToListAsync(); - - if (result.Any()) - { - _eventTypes ??= _serviceProvider.GetRequiredService().GetAllEventTypes() - .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type)); - - return result.OrderBy(o => o.CreationTime) - .Select(e => e.DeserializeJsonContent(_eventTypes.First(t => t.Name == e.EventTypeShortName))); - } - - return result; - } - - public async Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) - { - if (transaction == null) - throw new ArgumentNullException(nameof(transaction)); - - if (_eventLogContext.Database.CurrentTransaction == null) - await _eventLogContext.Database.UseTransactionAsync(transaction, Guid.NewGuid()); - - var eventLogEntry = new IntegrationEventLog(@event, _eventLogContext.Database.CurrentTransaction!.TransactionId); - await _eventLogContext.EventLogs.AddAsync(eventLogEntry); - await _eventLogContext.SaveChangesAsync(); - - CheckAndDetached(eventLogEntry); - } - - public Task MarkEventAsPublishedAsync(Guid eventId) - { - return UpdateEventStatus(eventId, IntegrationEventStates.Published, eventLog => - { - if (eventLog.State != IntegrationEventStates.InProgress) - { - _logger?.LogWarning( - "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", - IntegrationEventStates.Published, eventLog.State, eventLog.Id); - throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.Published}, the current State is {eventLog.State}, Id: {eventLog.Id}"); - } - }); - } - - public Task MarkEventAsInProgressAsync(Guid eventId) - { - return UpdateEventStatus(eventId, IntegrationEventStates.InProgress, eventLog => - { - if (eventLog.State != IntegrationEventStates.NotPublished && eventLog.State != IntegrationEventStates.PublishedFailed) - { - _logger?.LogWarning( - "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", - IntegrationEventStates.InProgress, eventLog.State, eventLog.Id); - throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.InProgress}, the current State is {eventLog.State}, Id: {eventLog.Id}"); - } - }); - } - - public Task MarkEventAsFailedAsync(Guid eventId) - { - return UpdateEventStatus(eventId, IntegrationEventStates.PublishedFailed, eventLog => - { - if (eventLog.State != IntegrationEventStates.InProgress) - { - _logger?.LogWarning( - "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", - IntegrationEventStates.PublishedFailed, eventLog.State, eventLog.Id); - throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.PublishedFailed}, the current State is {eventLog.State}, Id: {eventLog.Id}"); - } - }); - } - - public async Task DeleteExpiresAsync(DateTime expiresAt, int batchCount = 1000, CancellationToken token = default) - { - var eventLogs = _eventLogContext.EventLogs.Where(e => e.ModificationTime < expiresAt && e.State == IntegrationEventStates.Published) - .OrderBy(e => e.CreationTime).Take(batchCount); - _eventLogContext.EventLogs.RemoveRange(eventLogs); - await _eventLogContext.SaveChangesAsync(token); - - if (_eventLogContext.ChangeTracker.QueryTrackingBehavior != QueryTrackingBehavior.TrackAll) - { - foreach (var log in eventLogs) - { - _eventLogContext.Entry(log).State = EntityState.Detached; - } - } - } - - private async Task UpdateEventStatus(Guid eventId, IntegrationEventStates status, Action? action = null) - { - var eventLogEntry = _eventLogContext.EventLogs.FirstOrDefault(e => e.EventId == eventId); - if (eventLogEntry == null) - throw new ArgumentException(nameof(eventId)); - - action?.Invoke(eventLogEntry); - - - eventLogEntry.State = status; - eventLogEntry.ModificationTime = eventLogEntry.GetCurrentTime(); - if (status == IntegrationEventStates.InProgress) - eventLogEntry.TimesSent++; - - try - { - _eventLogContext.EventLogs.Update(eventLogEntry); - await _eventLogContext.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException ex) - { - throw new UserFriendlyException(ex.Message); - } - - CheckAndDetached(eventLogEntry); - } - - private void CheckAndDetached(IntegrationEventLog integrationEvent) - { - if (_eventLogContext.ChangeTracker.QueryTrackingBehavior != QueryTrackingBehavior.TrackAll) - { - _eventLogContext.Entry(integrationEvent).State = EntityState.Detached; - } - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs deleted file mode 100644 index 033654d2a..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; - -internal static class DbContextExtensions -{ - internal static IServiceCollection AddCustomMasaDbContext( - this IServiceCollection services, - Action contextAction) - where TDbContext : MasaDbContext - { - var optionsBuilder = new DbContextOptionsBuilder(); - contextAction.Invoke(optionsBuilder); - - services.AddDbContext(); - services.TryAddScoped(typeof(MasaDbContextOptions), serviceProvider => - { - return CreateMasaDbContextOptions(optionsBuilder.Options); - }); - return services; - } - - private static MasaDbContextOptions CreateMasaDbContextOptions( - DbContextOptions originOptions) - where TDbContext : MasaDbContext - { - return new MasaDbContextOptions(originOptions, new List(), new List()); - } -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs deleted file mode 100644 index c21f782a4..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; - -internal abstract class QueryFilterProvider : IQueryFilterProvider -{ - public abstract LambdaExpression OnExecuting(IMutableEntityType entityType); -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs deleted file mode 100644 index 65d132458..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; - -internal abstract class SaveChangesFilter : ISaveChangesFilter -{ - public abstract void OnExecuting(ChangeTracker changeTracker); -} diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj deleted file mode 100644 index c4d5d2b96..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj +++ /dev/null @@ -1,20 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - - diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md deleted file mode 100644 index 4ab7a3c7d..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md +++ /dev/null @@ -1,24 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF - -> Provide support for sending IntegrationEvent - -Example๏ผš - -```C# -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF -``` - -1. Add EventLogs.EF - -```C# -.AddDaprEventBus(options => -{ - options - // TODO - .UseEventLog(); -} -``` - -> Tip: CustomDbContext needs to inherit IntegrationEventLogContext \ No newline at end of file diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md deleted file mode 100644 index 76784f8b5..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md +++ /dev/null @@ -1,24 +0,0 @@ -ไธญ | [EN](README.md) - -## MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF - -> ไธบๅ‘้€IntegrationEventๆไพ›ๆ”ฏๆŒ - -็”จไพ‹๏ผš - -```C# -Install-Package MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF -``` - -1. ไฝฟ็”จEventLogs.EF - -```C# -.AddDaprEventBus(options => -{ - options - // TODO - .UseEventLog(); -} -``` - -> ๆ็คบ๏ผšCustomDbContext้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext diff --git a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs b/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs deleted file mode 100644 index 260da3f76..000000000 --- a/src/Dispatcher/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs +++ /dev/null @@ -1,18 +0,0 @@ -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; -global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; -global using MASA.Utils.Data.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.ChangeTracking; -global using Microsoft.EntityFrameworkCore.Metadata; -global using Microsoft.EntityFrameworkCore.Metadata.Builders; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using System; -global using System.Collections.Generic; -global using System.Data.Common; -global using System.Linq; -global using System.Linq.Expressions; -global using System.Threading.Tasks; diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs deleted file mode 100644 index 0ba4e22b0..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/Command.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Commands; - -public record Command(Guid Id, DateTime CreationTime) : ICommand -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - [JsonIgnore] - public IUnitOfWork? UnitOfWork { get; set; } - - public Command() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/CommandHandler.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/CommandHandler.cs deleted file mode 100644 index b73561b42..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Commands/CommandHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Commands; - -public abstract class CommandHandler : ICommandHandler, ISagaEventHandler - where TCommand : ICommand -{ - public abstract Task HandleAsync(TCommand @event); - - public virtual Task CancelAsync(TCommand @event) - { - return Task.CompletedTask; - } -} diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj deleted file mode 100644 index 21b30680e..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/MASA.Contrib.ReadWriteSpliting.CQRS.csproj +++ /dev/null @@ -1,19 +0,0 @@ -๏ปฟ - - - net6.0 - enable - enable - - - - - - - - - - - - - diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs deleted file mode 100644 index 44fac69fd..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/Query.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Queries; - -public abstract record Query(Guid Id, DateTime CreationTime) : IQuery - where TResult : notnull -{ - [JsonIgnore] - public Guid Id { get; } = Id; - - [JsonIgnore] - public DateTime CreationTime { get; } = CreationTime; - - public abstract TResult Result { get; set; } - - public Query() : this(Guid.NewGuid(), DateTime.UtcNow) { } -} diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs deleted file mode 100644 index 44256ba62..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/Queries/QueryHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Queries; - -public abstract class QueryHandler : IQueryHandler - where TQuery : IQuery - where TResult : notnull -{ - public abstract Task HandleAsync(TQuery @event); -} diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md deleted file mode 100644 index a2cb52a95..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.md +++ /dev/null @@ -1,88 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## CQRS - -Example๏ผš - -```C# -1. Define Command and Query base classes -2. Support the Handler of Saga mode, and provide the basic implementation of CommandHandler -``` - -```C# -Install-Package MASA.Contrib.ReadWriteSpliting.CQRS -``` - -##### Query๏ผš - -1. Define Query - -```C# -public class CatalogItemQuery : Query> -{ - public string Name { get; set; } = default!; - - public override List Result { get; set; } = default!; -} -``` - -2. Define QueryHandler - -```C# -public class CatalogQueryHandler : QueryHandler> -{ - private readonly ICatalogItemRepository _catalogItemRepository; - - public CatalogQueryHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; - - public async Task HandleAsync(CatalogItemQuery query) - { - query.Result = await _catalogItemRepository.GetListAsync(query.Name); - } -} -``` - -3. Send Query - -```c# -IEventBus eventBus;//Get IEventBus through DI -await eventBus.PublishAsync(new CatalogItemQuery() { Name = "Rolex" }); -``` - -> Tip: The generic type after Query is consistent with the return type of Result. You need to assign a value to Result in Handler so that the caller can get the result. - -##### Command - -1. Define Command - -```c# -public class CreateCatalogItemCommand : Command -{ - public string Name { get; set; } = default!; - - //todo -} -``` - -2. Add CommandHandler - -```c# -public class CatalogCommandHandler : CommandHandler -{ - private readonly ICatalogItemRepository _catalogItemRepository; - - public CatalogCommandHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; - - public async Task HandleAsync(CreateCatalogItemCommand command) - { - //todo - } -} -``` - -3. Send Command - -```C# -IEventBus eventBus;//Get IEventBus through DI -await eventBus.PublishAsync(new CreateCatalogItemCommand()); -``` \ No newline at end of file diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md deleted file mode 100644 index 6b84aebe2..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md +++ /dev/null @@ -1,88 +0,0 @@ -ไธญ | [EN](README.md) - -## CQRS - -็”จไพ‹๏ผš - -```C# -1. ๅฎšไน‰ไบ†CommandไธŽQueryๅŸบ็ฑป -2. ๆ”ฏๆŒSagaๆจกๅผ็š„Handler๏ผŒๅนถๆไพ›CommandHandlerๅŸบ็ก€ๅฎž็Žฐ -``` - -```C# -Install-Package MASA.Contrib.ReadWriteSpliting.CQRS -``` - -##### Query๏ผš - -1. ๅฎšไน‰Query - -```C# -public class CatalogItemQuery : Query> -{ - public string Name { get; set; } = default!; - - public override List Result { get; set; } = default!; -} -``` - -2. ๅฎšไน‰QueryHandler - -```C# -public class CatalogQueryHandler : QueryHandler> -{ - private readonly ICatalogItemRepository _catalogItemRepository; - - public CatalogQueryHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; - - public async Task HandleAsync(CatalogItemQuery query) - { - query.Result = await _catalogItemRepository.GetListAsync(query.Name); - } -} -``` - -3. ๅ‘้€Query - -```c# -IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus -await eventBus.PublishAsync(new CatalogItemQuery() { Name = "Rolex" }); -``` - -> ๆ็คบ๏ผšQueryๅŽ็š„ๆณ›ๅž‹ไธŽResult็š„่ฟ”ๅ›ž็ฑปๅž‹ไฟๆŒไธ€่‡ด๏ผŒ้œ€่ฆๅ†HandlerไธญไธบResult่ต‹ๅ€ผ๏ผŒไปฅไพฟ่ฐƒ็”จๆ–นๅพ—ๅˆฐ็ป“ๆžœ - -##### Command - -1. ๅฎšไน‰ Command - -```c# -public class CreateCatalogItemCommand : Command -{ - public string Name { get; set; } = default!; - - //todo -} -``` - -2. ๆทปๅŠ  CommandHandler - -```c# -public class CatalogCommandHandler : CommandHandler -{ - private readonly ICatalogItemRepository _catalogItemRepository; - - public CatalogCommandHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; - - public async Task HandleAsync(CreateCatalogItemCommand command) - { - //todo - } -} -``` - -3. ๅ‘้€ Command - -```C# -IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus -await eventBus.PublishAsync(new CreateCatalogItemCommand()); -``` diff --git a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs b/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs deleted file mode 100644 index 79632aa72..000000000 --- a/src/ReadWriteSpliting/CQRS/MASA.Contrib.ReadWriteSpliting.CQRS/_Imports.cs +++ /dev/null @@ -1,6 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.ReadWriteSpliting.CQRS.Commands; -global using MASA.BuildingBlocks.ReadWriteSpliting.CQRS.Queries; -global using System.Text.Json.Serialization; - diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj b/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj deleted file mode 100644 index 0c88a1c69..000000000 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/MASA.Contrib.Service.MinimalAPIs.csproj +++ /dev/null @@ -1,18 +0,0 @@ -๏ปฟ - - - net6.0 - enable - - - - - - - - - - - - - diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md b/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md deleted file mode 100644 index 6560eea7f..000000000 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.md +++ /dev/null @@ -1,53 +0,0 @@ -[ไธญ](README.zh-CN.md) | EN - -## MinimalAPI - -Original usage๏ผš - -```C# -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); -app.MapGet("/api/v1/helloworld", ()=>"Hello World"); -app.Run(); -``` - -Example๏ผš - -```c# -Install-Package MASA.Contrib.Service.MinimalAPIs -``` - -1. Add MinimalAPI - -```c# -var builder = WebApplication.CreateBuilder(args); -var app = builder.Services - .AddServices(builder); -``` - -2. Customize Service and inherit ServiceBase - -```c# -public class IntegrationEventService : ServiceBase -{ - public IntegrationEventService(IServiceCollection services) : base(services) - { - App.MapGet("/api/v1/payment/HelloWorld", HelloWorld); - } - - public string HelloWorld() - { - return "Hello World"; - } -} -``` - -> Tip: The service that inherits ServiceBase is registered in singleton mode, if you need to obtain it from DI - -```C# -public async Task DeleteBasketByIdAsync(string id, [FromServices] IBasketRepository repository) -{ - await repository.DeleteBasketAsync(id); -} -``` - diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md b/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md deleted file mode 100644 index ef8c8bca7..000000000 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/README.zh-CN.md +++ /dev/null @@ -1,53 +0,0 @@ -ไธญ | [EN](README.md) - -## MinimalAPI - -ๅŽŸๅง‹็”จๆณ•๏ผš - -```C# -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); -app.MapGet("/api/v1/helloworld", ()=>"Hello World"); -app.Run(); -``` - -็”จไพ‹๏ผš - -```c# -Install-Package MASA.Contrib.Service.MinimalAPIs -``` - -1. ๆทปๅŠ MinimalAPI - -```c# -var builder = WebApplication.CreateBuilder(args); -var app = builder.Services - .AddServices(builder); -``` - -2. ่‡ชๅฎšไน‰Serviceๅนถ็ปงๆ‰ฟServiceBase๏ผŒๅฆ‚๏ผš - -```c# -public class IntegrationEventService : ServiceBase -{ - public IntegrationEventService(IServiceCollection services) : base(services) - { - App.MapGet("/api/v1/payment/HelloWorld", HelloWorld); - } - - public string HelloWorld() - { - return "Hello World"; - } -} -``` - -> ๆ็คบ๏ผš็ปงๆ‰ฟServiceBase็š„ๆœๅŠกไธบๅ•ไพ‹ๆจกๅผๆณจๅ†Œ๏ผŒๅฆ‚ๆžœ้œ€่ฆไปŽDI่Žทๅ–่Žทๅ– - -```C# -public async Task DeleteBasketByIdAsync(string id, [FromServices] IBasketRepository repository) -{ - await repository.DeleteBasketAsync(id); -} -``` - diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs b/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs deleted file mode 100644 index 2ef353c06..000000000 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceBase.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace MASA.Contrib.Service.MinimalAPIs; - -public abstract class ServiceBase : IService -{ - private ServiceProvider _serviceProvider = default!; - - public WebApplication App => _serviceProvider.GetRequiredService(); - - public string BaseUri { get; } - - public IServiceCollection Services { get; protected set; } - - public ServiceBase(IServiceCollection services) - { - Services = services; - _serviceProvider = services.BuildServiceProvider(); - } - - public ServiceBase(IServiceCollection services, string baseUri) - { - BaseUri = baseUri; - Services = services; - _serviceProvider = services.BuildServiceProvider(); - } - - public TService? GetService() => _serviceProvider.GetService(); - - public TService GetRequiredService() - where TService : notnull - => Services.BuildServiceProvider().GetRequiredService(); - - #region Map GET,POST,PUT,DELETE - - /// - /// Adds a to the that matches HTTP GET requests - /// for the specified pattern, a combination of and or name. - /// - /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. - /// The custom uri. It is a part of pattern if it is not null. - /// Determines whether to remove the string 'Async' at the end. - /// A that can be used to further customize the endpoint. - protected RouteHandlerBuilder MapGet(Delegate handler, string? customUri = null, bool trimEndAsync = true) - { - customUri ??= FormatAction(handler.Method.Name, trimEndAsync); - - var pattern = CombineUris(BaseUri, customUri); - - return App.MapGet(pattern, handler); - } - - /// - /// Adds a to the that matches HTTP POST requests - /// for the specified pattern, a combination of and or name. - /// - /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. - /// The custom uri. It is a part of pattern if it is not null. - /// Determines whether to remove the string 'Async' at the end. - /// A that can be used to further customize the endpoint. - protected RouteHandlerBuilder MapPost(Delegate handler, string? customUri = null, bool trimEndAsync = true) - { - customUri ??= FormatAction(handler.Method.Name, trimEndAsync); - - var pattern = CombineUris(BaseUri, customUri); - - return App.MapPost(pattern, handler); - } - - /// - /// Adds a to the that matches HTTP PUT requests - /// for the specified pattern, a combination of and or name. - /// - /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. - /// The custom uri. It is a part of pattern if it is not null. - /// Determines whether to remove the string 'Async' at the end. - /// A that can be used to further customize the endpoint. - protected RouteHandlerBuilder MapPut(Delegate handler, string? customUri = null, bool trimEndAsync = true) - { - customUri ??= FormatAction(handler.Method.Name, trimEndAsync); - - var pattern = CombineUris(BaseUri, customUri); - - return App.MapPut(pattern, handler); - } - - /// - /// Adds a to the that matches HTTP DELETE requests - /// for the specified pattern, a combination of and or name. - /// - /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. - /// The custom uri. It is a part of pattern if it is not null. - /// Determines whether to remove the string 'Async' at the end. - /// A that can be used to further customize the endpoint. - protected RouteHandlerBuilder MapDelete(Delegate handler, string? customUri = null, bool trimEndAsync = true) - { - customUri ??= FormatAction(handler.Method.Name, trimEndAsync); - - var pattern = CombineUris(BaseUri, customUri); - - return App.MapDelete(pattern, handler); - } - - private static string FormatAction(string action, bool trimEndAsync) - { - if (trimEndAsync && action.EndsWith("Async")) - { - var i = action.LastIndexOf("Async", StringComparison.Ordinal); - action = action[..i]; - } - - return action; - } - - private static string CombineUris(params string[] uris) - { - return string.Join("/", uris.Select(u => u.Trim('/'))); - } - - #endregion -} \ No newline at end of file diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs b/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs deleted file mode 100644 index 98a21e7a1..000000000 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace MASA.Contrib.Service.MinimalAPIs; - -public static class ServiceCollectionExtensions -{ - /// - /// Add all classes that inherit from ServiceBase to Microsoft.Extensions.DependencyInjection.IServiceCollection - /// Notice: this method must be last call. - /// - /// The Microsoft.AspNetCore.Builder.WebApplicationBuilder. - /// - public static WebApplication AddServices(this WebApplicationBuilder builder) - => builder.Services.AddServices(builder); - - /// - /// Add all classes that inherit from ServiceBase to Microsoft.Extensions.DependencyInjection.IServiceCollection - /// Notice: this method must be last call. - /// - /// The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service to. - /// The Microsoft.AspNetCore.Builder.WebApplicationBuilder. - /// - public static WebApplication AddServices(this IServiceCollection services, WebApplicationBuilder builder) - { - if (services.All(service => service.ImplementationType != typeof(MinimalApisMarkerService))) - { - services.AddSingleton(); - services.TryAddScoped(sp => services); - - services.AddSingleton(new Lazy(() => builder.Build(), LazyThreadSafetyMode.ExecutionAndPublication)) - .AddTransient(serviceProvider => serviceProvider.GetRequiredService>().Value); - - services.AddServices(true, AppDomain.CurrentDomain.GetAssemblies()); - } - - var serviceProvider = services.BuildServiceProvider(); - return serviceProvider.GetRequiredService(); - } - - private class MinimalApisMarkerService - { - - } -} diff --git a/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs b/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs deleted file mode 100644 index a6b33566f..000000000 --- a/src/Service/MASA.Contrib.Service.MinimalAPIs/_Imports.cs +++ /dev/null @@ -1,8 +0,0 @@ -global using MASA.BuildingBlocks.Service.MinimalAPIs; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Routing; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using System; -global using System.Linq; -global using System.Threading; diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs deleted file mode 100644 index a8bc038b9..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs +++ /dev/null @@ -1,388 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests; - -[TestClass] -public class DccClientTest -{ - private Mock _client; - private IServiceCollection _services; - private IServiceProvider _serviceProvider => _services.BuildServiceProvider(); - private JsonSerializerOptions _jsonSerializerOptions; - private DccSectionOptions _dccSectionOptions; - private CustomTrigger _trigger; - - [TestInitialize] - public void Initialize() - { - _client = new Mock(); - _services = new ServiceCollection(); - _jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true - }; - _dccSectionOptions = new DccSectionOptions() - { - Environment = "Test", - Cluster = "Default", - AppId = "DccTest", - ConfigObjects = new List() - { - "Test1" - }, - Secret = "" - }; - _trigger = new CustomTrigger(_jsonSerializerOptions); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestGetRawAsync(string environment, string cluster, string appId, string configObject) - { - Action valueChanged = delegate (string? val) { }; - _client.Setup(client => client.GetAsync(It.IsAny(), valueChanged).Result).Returns(() => null).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () - => await client.GetRawAsync(environment, cluster, appId, configObject, valueChanged), "configObject invalid" - ); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => "test").Verifiable(); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () - => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) - ); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => "{}").Verifiable(); - await Assert.ThrowsExceptionAsync(async () - => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), "configObject invalid" - ); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new - { - ConfigFormat = "1", - Content = "" - }.Serialize(_jsonSerializerOptions)).Verifiable(); - await Assert.ThrowsExceptionAsync(async () - => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), "configObject invalid" - ); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease - { - ConfigFormat = (ConfigFormats)5, - Content = "" - }.Serialize(_jsonSerializerOptions)).Verifiable(); - await Assert.ThrowsExceptionAsync( - async () => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), - "Unsupported configuration type"); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestGetRawAsyncByJson(string environment, string cluster, string appId, string configObject) - { - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - - var brand = new Brands("Apple"); - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsTrue(ret.Raw == brand.Serialize(_jsonSerializerOptions)); - Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Json); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestGetRawAsyncByText(string environment, string cluster, string appId, string configObject) - { - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Text, - Content = "test" - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsTrue(ret.Raw == "test"); - Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Text); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestGetRawAsyncByProperty(string environment, string cluster, string appId, string configObject) - { - List properties = new List() - { - new() - { - Key = "Brand", - Value = "Microsoft" - } - }; - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Text, - Content = properties.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsTrue(ret.Raw == properties.Serialize(_jsonSerializerOptions)); - Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Text); - } - - [TestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task GetAsyncByJson(string environment, string cluster, string appId, string configObject) - { - var brand = new Brands("Microsoft"); - var newBrand = new Brands("Microsoft2"); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => - { - _trigger.Formats = ConfigFormats.Json; - _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); - _trigger.Action = action; - }); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var ret = await client.GetAsync(environment, cluster, appId, configObject, (Brands br) => - { - Assert.IsTrue(br.Id == newBrand.Id); - Assert.IsTrue(br.Name == newBrand.Name); - }); - Assert.IsNotNull(ret); - - Assert.IsTrue(ret.Serialize(_jsonSerializerOptions).Equals(brand.Serialize(_jsonSerializerOptions))); - _trigger.Execute(); - - ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - - Assert.IsTrue(ret.Id == newBrand.Id && ret.Name == newBrand.Name); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => - { - _trigger.Formats = ConfigFormats.Json; - newBrand.Name = "Masa"; - _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); - _trigger.Action = action; - }); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - Assert.IsTrue(ret.Id == brand.Id && ret.Name == brand.Name); - _trigger.Execute(); - ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsTrue(ret.Id == newBrand.Id && ret.Name == "Masa"); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - Assert.IsTrue(ret.Id == brand.Id && ret.Name == brand.Name); - - Initialize(); - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Verifiable(); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () => - { - await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - }); - } - - [TestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task GetAsyncByText(string environment, string cluster, string appId, string configObject) - { - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Text, - Content = "test" - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () => - { - await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - }); - - Initialize(); - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Text, - Content = "1" - }.Serialize(_jsonSerializerOptions)).Verifiable(); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - Assert.IsTrue(await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()) == 1); - } - - [TestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task GetAsyncByProperty(string environment, string cluster, string appId, string configObject) - { - var brand = new List() - { - new() - { - Key = "Id", - Value = Guid.NewGuid().ToString(), - }, - new() - { - Key = "Name", - Value = "Microsoft" - } - }; - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Properties, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - - Assert.IsTrue(ret.Id.ToString() == brand.Where(b => b.Key == "Id").Select(t => t.Value).FirstOrDefault() && - ret.Name == brand.Where(b => b.Key == "Name").Select(t => t.Value).FirstOrDefault()); - } - - [TestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task GetDynamicAsyncByJson(string environment, string cluster, string appId, string configObject) - { - var brand = new Brands("Microsoft"); - var newBrand = new Brands("Microsoft2"); - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => - { - _trigger.Formats = ConfigFormats.Json; - _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); - _trigger.Action = action; - }).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, (dynamic obj) => - { - Assert.IsTrue((obj.Id + "") == newBrand.Id.ToString()); - - Assert.IsTrue(obj.Name == newBrand.Name); - }); - Assert.IsNotNull(ret); - - Assert.IsTrue(ret.Id == brand.Id.ToString()); - - Assert.IsTrue(ret.Name == brand.Name); - - _trigger.Execute(); - - ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - Assert.IsTrue(ret.Id == newBrand.Id.ToString() && ret.Name == newBrand.Name); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => - { - _trigger.Formats = ConfigFormats.Json; - _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); - _trigger.Action = action; - }).Verifiable(); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny()); - Assert.IsNotNull(ret); - Assert.IsTrue(ret.Id == brand.Id.ToString()); - Assert.IsTrue(ret.Name == brand.Name); - _trigger.Execute(); - - ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - Assert.IsTrue(ret.Id == newBrand.Id.ToString() && ret.Name == newBrand.Name); - - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Json, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)); - client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - Assert.IsTrue(ret.Id == brand.Id.ToString() && ret.Name == brand.Name); - } - - [TestMethod] - [DataRow("DccOptions.ManageServiceAddress", "http://localhost:6379")] - [DataRow("DccOptions.RedisOptions.DefaultDatabase", "0")] - [DataRow("DccOptions.RedisOptions.Password", "")] - public async Task GetDynamicAsync(string key, string value) - { - var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build(); - _services.AddSingleton(configuration); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var res = (await client.GetDynamicAsync(key)); - Assert.IsTrue(res + "" == value); - } - - [TestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task GetDynamicAsyncByText(string environment, string cluster, string appId, string configObject) - { - string result = "Test"; - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Text, - Content = result - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () => - { - await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); - }); - } - - [TestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task GetDynamicAsyncByProperty(string environment, string cluster, string appId, string configObject) - { - var brand = new List() - { - new() - { - Key = "Id", - Value = Guid.NewGuid().ToString(), - }, - new() - { - Key = "Name", - Value = "Microsoft" - } - }; - _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() - { - ConfigFormat = ConfigFormats.Properties, - Content = brand.Serialize(_jsonSerializerOptions) - }.Serialize(_jsonSerializerOptions)).Verifiable(); - var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); - var ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); - Assert.IsNotNull(ret); - - Assert.IsTrue(ret.Id == brand.Where(b => b.Key == "Id").Select(b => b.Value).FirstOrDefault()); - Assert.IsTrue(ret.Name == brand.Where(b => b.Key == "Name").Select(b => b.Value).FirstOrDefault()); - } -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs deleted file mode 100644 index baecfe2bf..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs +++ /dev/null @@ -1,162 +0,0 @@ -using MASA.Contrib.BasicAbility.Dcc.Internal; -using MASA.Utils.Caller.Core; -using System.Net; - -namespace MASA.Contrib.BasicAbility.Dcc.Tests; - -[TestClass] -public class DccManageTest -{ - private DccSectionOptions _dccSectionOptions; - private JsonSerializerOptions _jsonSerializerOptions; - private Mock _callerProvider; - - [TestInitialize] - public void Initialize() - { - _dccSectionOptions = new DccSectionOptions() - { - Environment = "Test", - Cluster = "Default", - AppId = "DccTest", - ConfigObjects = new List() - { - "Test1" - }, - Secret = "Secret" - }; - _jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true - }; - _callerProvider = new Mock(); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestUpdateAsync(string environment, string cluster, string appId, string configObject) - { - var brand = new Brands("Microsoft"); - _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(brand.Serialize(_jsonSerializerOptions)) - }).Verifiable(); - - var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); - await manage.UpdateAsync(environment, cluster, appId, configObject, brand); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestUpdateAsyncAndError(string environment, string cluster, string appId, string configObject) - { - var brand = new Brands("Microsoft"); - - _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() - { - StatusCode = HttpStatusCode.ExpectationFailed, - Content = new StringContent("error") - }).Verifiable(); - - var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () => await manage.UpdateAsync(environment, cluster, appId, configObject, brand)); - } - - [DataTestMethod] - [DataRow("Test", "Default", "DccTest", "Brand")] - public async Task TestUpdateAsyncAndCustomError(string environment, string cluster, string appId, string configObject) - { - var brand = new Brands("Microsoft"); - _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() - { - StatusCode = (HttpStatusCode)299, - Content = new StringContent("custom error") - }).Verifiable(); - - var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); - await Assert.ThrowsExceptionAsync(async () => await manage.UpdateAsync(environment, cluster, appId, configObject, brand)); - } - - [DataTestMethod] - [DataRow("DccTest", "Secret")] - [DataRow("DccTest2", "Secret2")] - [DataRow("DccTest3", "")] - public void TestGetSecret(string appId, string secret) - { - var api = new CustomConfigurationAPI(_dccSectionOptions, new List() - { - new() - { - Environment = "Test2", - Cluster = "Default2", - AppId = "DccTest2", - ConfigObjects = new List() - { - "Test12" - }, - Secret = "Secret2" - } - }); - if (string.IsNullOrEmpty(secret)) - Assert.ThrowsException(() => api.GetSecret(appId)); - else - Assert.IsTrue(api.GetSecret(appId) == secret); - } - - [DataTestMethod] - [DataRow("Test2", "Test2")] - [DataRow("", "Test")] - public void TestGetEnvironment(string environment, string outEnvironment) - { - var api = new CustomConfigurationAPI(_dccSectionOptions, null); - Assert.IsTrue(api.GetEnvironment(environment) == outEnvironment); - } - - [DataTestMethod] - [DataRow("CustomCluster", "CustomCluster")] - [DataRow("", "Default")] - public void GetCluster(string cluster, string outCluster) - { - var api = new CustomConfigurationAPI(_dccSectionOptions, null); - Assert.IsTrue(api.GetCluster(cluster) == outCluster); - } - - [DataTestMethod] - [DataRow("CustomAppid", "CustomAppid")] - [DataRow("", "DccTest")] - public void GetAppid(string appId, string outAppid) - { - var api = new CustomConfigurationAPI(_dccSectionOptions, null); - Assert.IsTrue(api.GetAppId(appId) == outAppid); - } - - [DataTestMethod] - [DataRow("configObject", "configObject")] - [DataRow("", "")] - public void GetConfigObject(string configObject, string outConfigObject) - { - var api = new CustomConfigurationAPI(_dccSectionOptions, null); - if (string.IsNullOrEmpty(configObject)) - Assert.ThrowsException(() => api.GetConfigObject(configObject)); - else - Assert.IsTrue(api.GetConfigObject(configObject) == outConfigObject); - } -} - -public class CustomConfigurationAPI : ConfigurationAPIBase -{ - public CustomConfigurationAPI(DccSectionOptions defaultSectionOption, List? expandSectionOptions) : base(defaultSectionOption, expandSectionOptions) - { - } - - public new string GetSecret(string appId) => base.GetSecret(appId); - - public new string GetEnvironment(string environment) => base.GetEnvironment(environment); - - public new string GetCluster(string cluster) => base.GetCluster(cluster); - - public new string GetAppId(string appId) => base.GetAppId(appId); - - public new string GetConfigObject(string configObject) => base.GetConfigObject(configObject); -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs deleted file mode 100644 index 8d5394a69..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/DccTest.cs +++ /dev/null @@ -1,799 +0,0 @@ -using MASA.Utils.Caching.Core.Interfaces; -using MASA.Utils.Caching.Core.Models; -using MASA.Utils.Caching.DistributedMemory.Models; -using MASA.Utils.Caller.Core; -using MASA.Utils.Caller.HttpClient; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; - -namespace MASA.Contrib.BasicAbility.Dcc.Tests; - -[TestClass] -public class DccTest -{ - private string DEFAULT_CLIENT_NAME = "masa.plugins.caching.dcc"; - private Mock _masaConfigurationBuilder; - private JsonSerializerOptions _jsonSerializerOptions; - private IServiceCollection _services; - - private Mock _memoryCacheClientFactory; - private Mock _memoryCache; - private Mock _distributedCacheClient; - private const string DefaultEnvironmentName = "ASPNETCORE_ENVIRONMENT"; - private const string DEFAULT_SUBSCRIBE_KEY_PREFIX = "masa.dcc:"; - - [TestInitialize] - public void Initialize() - { - _masaConfigurationBuilder = new Mock(); - _memoryCacheClientFactory = new Mock(); - _memoryCache = new Mock(); - _distributedCacheClient = new Mock(); - _services = new ServiceCollection(); - _jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true - }; - } - - [TestMethod] - public void TestErrorDccSection() - { - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary()).Verifiable(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(new ServiceCollection())); - } - - [TestMethod] - public void TestTryAddConfigurationApiClient() - { - _memoryCacheClientFactory.Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)).Returns(() => null!).Verifiable(); - _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); - MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), null!); - Assert.IsTrue(_services.Count(service => service.ServiceType == typeof(IConfigurationApiClient) && service.Lifetime == ServiceLifetime.Singleton) == 1); - Assert.ThrowsException(() => - { - var clienties = _services.BuildServiceProvider().GetServices(); - }); - - _services = new ServiceCollection(); - _memoryCacheClientFactory - .Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)) - .Returns(() => new MemoryCacheClient(_memoryCache.Object, _distributedCacheClient.Object, SubscribeKeyTypes.ValueTypeFullNameAndKey)) - .Verifiable(); - _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); - MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), new JsonSerializerOptions() - { - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }); - - var clienties = _services.BuildServiceProvider().GetServices(); - Assert.IsTrue(clienties.Count() == 1); - - _services = new ServiceCollection(); - _memoryCacheClientFactory - .Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)) - .Returns(() => new MemoryCacheClient(_memoryCache.Object, _distributedCacheClient.Object, Utils.Caching.Core.Models.SubscribeKeyTypes.ValueTypeFullNameAndKey)) - .Verifiable(); - _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); - MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), _jsonSerializerOptions); - MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), _jsonSerializerOptions); - clienties = _services.BuildServiceProvider().GetServices(); - Assert.IsTrue(clienties.Count() == 1); - } - - [TestMethod] - public void TestTryAddConfigurationApiManage() - { - Mock httpClientFactory = new(); - _services.AddSingleton(httpClientFactory.Object); - _services.AddCaller(options => options.UseHttpClient()); - - MasaConfigurationExtensions.TryAddConfigurationApiManage(_services, new DccSectionOptions(), new List()); - MasaConfigurationExtensions.TryAddConfigurationApiManage(_services, new DccSectionOptions(), new List()); - Assert.IsTrue(_services.Count(service => service.ServiceType == typeof(IConfigurationApiManage) && service.Lifetime == ServiceLifetime.Singleton) == 1); - var serviceProvider = _services.BuildServiceProvider(); - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - } - - [TestMethod] - public void TestUseDCCAndErrorSection() - { - _services.AddCaller(options => options.UseHttpClient()); - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary()).Verifiable(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, "", null, null), "configureOptions"); - } - - [TestMethod] - public void TestUseDCCAndNullDccConfigurationOption() - { - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => null!, option => - { - option.AppId = "Test"; - option.Environment = "Test"; - option.ConfigObjects = new List() { "Te" }; - }, null), "configureOptions"); - } - - [TestMethod] - public void TestCustomCaller() - { - var response = JsonSerializer.Serialize(new PublishRelease() - { - Content = string.Empty, - ConfigFormat = ConfigFormats.Text - }); - Mock memoryCacheClient = new(); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - - var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - _masaConfigurationBuilder.Object.UseDcc(_services, () => new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }, option => - { - option.AppId = "Test"; - option.Environment = "Test"; - option.ConfigObjects = new List() - { - "Settings" - }; - }, null, jsonSerializerOption => - { - jsonSerializerOption.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - }, option => - { - option.UseHttpClient(builder => - { - builder.Name = "CustomHttpClient"; - builder.Configure = opt => opt.BaseAddress = new Uri("https://github.com"); - }); - }); - var callerProvider = _services.BuildServiceProvider().GetRequiredService().CreateClient("CustomHttpClient"); - Assert.IsNotNull(callerProvider); - } - - [TestMethod] - public void TestUseDCCAndEmptyDccServiceAddress() - { - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "", - }; - }, null!, null), "DccServiceAddress"); - } - - [TestMethod] - public void TestUseDCCAndErrorDccService() - { - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = null! - } - }; - }, null!, null), "Servers"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions - { - Servers = new List() - } - }; - }, null!, null), "Servers"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions - { - Servers = new List() - { - new() - { - Host="", - Port=8080 - } - } - } - }; - }, null!, null), "Servers"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host="localhost", - Port=-1 - } - } - } - }; - }, null!, null), "Servers"); - } - - [TestMethod] - public void TestUseDCCAndErrorDefaultSectionOption() - { - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, null!, null), "defaultSectionOptions"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = ""; - }, null), "AppId cannot be empty"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = null!; - }, null), "ConfigObjects cannot be empty"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List(); - }, null), "ConfigObjects cannot be empty"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List() - { - "Brand" - }; - }, null), "Error getting environment information, please make sure the value of ASPNETCORE_ENVIRONMENT has been configured"); - } - - [TestMethod] - public void TestUseDCCAndErrorExpansionSectionOptions() - { - System.Environment.SetEnvironmentVariable(DefaultEnvironmentName, "Test"); - - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List() - { - "Brand" - }; - }, option => - { - option.ExpandSections = new List() - { - new() - { - AppId = "Test2", - } - }; - }), "ConfigObjects in the extension section cannot be empty"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List() - { - "Brand" - }; - }, option => - { - option.ExpandSections = new List() - { - new() - { - AppId = "Test2", - ConfigObjects=new List() - } - }; - }), "ConfigObjects in the extension section cannot be empty"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List() - { - "Brand" - }; - }, option => - { - option.ExpandSections = new List() - { - new() - { - AppId = "Test", - ConfigObjects=new List() - { - "Settings" - } - } - }; - }), "The current section already exists, no need to mount repeatedly"); - - _services = new ServiceCollection(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List() - { - "Brand" - }; - }, option => - { - option.ExpandSections = new List() - { - new() - { - AppId = "Test2", - ConfigObjects=new List() - { - "Settings" - } - }, - new() - { - AppId = "Test2", - ConfigObjects=new List() - { - "Settings" - } - } - }; - }), "The current section already exists, no need to mount repeatedly"); - } - - [DataTestMethod] - [DataRow("Development", "Default", "WebApplication1", "Brand")] - public void TestUseDCCAndSuccess(string environment, string cluster, string appId, string configObject) - { - System.Environment.SetEnvironmentVariable(DefaultEnvironmentName, "Test"); - var brand = new Brands("Microsoft"); - var response = JsonSerializer.Serialize(new PublishRelease() - { - Content = System.Text.Json.JsonSerializer.Serialize(brand), - ConfigFormat = ConfigFormats.Json - }); - Mock memoryCacheClient = new(); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - - var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - _masaConfigurationBuilder.Object.UseDcc(_services, () => - { - return new DccConfigurationOptions() - { - ManageServiceAddress = "https://github.com", - RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() - { - Servers = new List() - { - new() - { - Host = "localhost", - Port = 6379 - } - } - } - }; - }, option => - { - option.AppId = "Test"; - option.ConfigObjects = new List() - { - "Brand" - }; - }, null); - var optionFactory = _services.BuildServiceProvider().GetRequiredService>(); - var option = optionFactory.Create(DEFAULT_CLIENT_NAME); - - Assert.IsTrue(option.SubscribeKeyType == SubscribeKeyTypes.SpecificPrefix); - - Assert.IsTrue(option.SubscribeKeyPrefix == DEFAULT_SUBSCRIBE_KEY_PREFIX); - } - - [DataTestMethod] - [DataRow("Development", "Default", "WebApplication1", "Brand")] - public void TestUseDccAndSingleSection(string environment, string cluster, string appId, string configObject) - { - CustomTrigger trigger = new CustomTrigger(_jsonSerializerOptions); - var brand = new Brands("Microsoft"); - var newBrand = new Brands("Masa"); - - var response = JsonSerializer.Serialize(new PublishRelease() - { - Content = brand.Serialize(_jsonSerializerOptions), - ConfigFormat = ConfigFormats.Text - }); - Mock memoryCacheClient = new(); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - var chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - - _masaConfigurationBuilder.Object.UseDcc(_services); - Assert.IsTrue( - configurationApiClient - .GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) - .GetAwaiter() - .GetResult().Raw == brand.Serialize(_jsonSerializerOptions)); - trigger.Execute(); - - Initialize(); - - Dictionary masaDic = new Dictionary() - { - { "Id", Guid.NewGuid().ToString() }, - { "Name", "Masa" } - }; - response = JsonSerializer.Serialize(new PublishRelease() - { - Content = masaDic.Serialize(_jsonSerializerOptions), - ConfigFormat = ConfigFormats.Json - }); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - - _masaConfigurationBuilder.Object.UseDcc(_services); - Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).Result.Raw == masaDic.Serialize(_jsonSerializerOptions)); - - Initialize(); - - response = JsonSerializer.Serialize(new PublishRelease() - { - Content = "Test", - ConfigFormat = ConfigFormats.Text - }); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - - _masaConfigurationBuilder.Object.UseDcc(_services); - Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).GetAwaiter().GetResult().Raw == "Test"); - - Initialize(); - - response = JsonSerializer.Serialize(new PublishRelease() - { - Content = null, - ConfigFormat = ConfigFormats.Text - }); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - - _masaConfigurationBuilder.Object.UseDcc(_services); - Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).GetAwaiter().GetResult().Raw == null); - - Initialize(); - - response = JsonSerializer.Serialize(new PublishRelease() - { - Content = "Test", - ConfigFormat = (ConfigFormats)4 - }); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services)); - } - - [TestMethod] - public void TestUseDccAndExpandSections() - { - var brand = new Brands("Microsoft"); - var response = JsonSerializer.Serialize(new PublishRelease() - { - Content = JsonSerializer.Serialize(brand), - ConfigFormat = ConfigFormats.Json - }); - Mock memoryCacheClient = new(); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - - var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - var chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("expandSections.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - - _masaConfigurationBuilder.Object.UseDcc(_services); - - var result = configurationApiClient.GetRawAsync("Test", "Default", "DccTest", "Test1", It.IsAny>()) - .ConfigureAwait(false).GetAwaiter().GetResult(); - Assert.IsTrue(result.Raw == JsonSerializer.Serialize(brand)); - } - - [DataTestMethod] - [DataRow("Development", "Default", "WebApplication1", "Brand")] - public void TestUseMultiDcc(string environment, string cluster, string appId, string configObject) - { - var brand = new Brands("Microsoft"); - var response = JsonSerializer.Serialize(new PublishRelease() - { - Content = JsonSerializer.Serialize(brand), - ConfigFormat = ConfigFormats.Json - }); - Mock memoryCacheClient = new(); - memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) - .Returns(() => response); - - var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), - memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); - _services.AddSingleton(configurationApiClient); - - var chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true); - - _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() - { - { "Appsettings",chainedConfiguration.Build() } - }).Verifiable(); - - _masaConfigurationBuilder.Object.UseDcc(_services).UseDcc(_services); - var result = configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) - .ConfigureAwait(false).GetAwaiter().GetResult(); - Assert.IsTrue(result.Raw == JsonSerializer.Serialize(brand)); - - var httpClient = _services.BuildServiceProvider().GetRequiredService().CreateClient(DEFAULT_CLIENT_NAME); - Assert.IsTrue(httpClient.BaseAddress!.ToString() == "http://localhost:6379/"); - } - -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs deleted file mode 100644 index 7f5827b59..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Common; - -internal static class SerializeCommon -{ - public static string Serialize(this object obj, JsonSerializerOptions? jsonSerializerOptions) - => JsonSerializer.Serialize(obj, jsonSerializerOptions); -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs deleted file mode 100644 index 01735427f..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Config; - -internal class Property -{ - public string Key { get; set; } = default!; - - public string Value { get; set; } = default!; -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs deleted file mode 100644 index 5cafa5fe5..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal; - -internal class PublishRelease -{ - public ConfigFormats ConfigFormat { get; set; } - - public string? Content { get; set; } -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs deleted file mode 100644 index 93088edcc..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal; - -public class CustomTrigger -{ - private JsonSerializerOptions _jsonSerializerOptions; - - public CustomTrigger(JsonSerializerOptions jsonSerializerOptions) - { - _jsonSerializerOptions = jsonSerializerOptions; - } - - internal ConfigFormats Formats { get; set; } - - internal string Content { get; set; } - - internal Action Action { get; set; } - - internal void Execute() - { - Action?.Invoke(new PublishRelease() - { - ConfigFormat = Formats, - Content = Content - }.Serialize(_jsonSerializerOptions)); - } -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs deleted file mode 100644 index 15e6e5d3b..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; - -internal enum ConfigFormats -{ - Properties = 1, - Text, - Json -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs deleted file mode 100644 index d394e9e06..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Model; - -internal class Brands -{ - public Guid Id { get; init; } - - public string Name { get; set; } - - public Brands() - => this.Id = Guid.NewGuid(); - - public Brands(string Name) : this() - => this.Name = Name; -} diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj b/test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj deleted file mode 100644 index 8d6f3f437..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/MASA.Contrib.BasicAbility.Dcc.Tests.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - Always - - - Always - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs b/test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs deleted file mode 100644 index 8161f76a2..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/_Imports.cs +++ /dev/null @@ -1,14 +0,0 @@ -global using MASA.BuildingBlocks.Configuration; -global using MASA.Contrib.BasicAbility.Dcc.Options; -global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal; -global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Common; -global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Config; -global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; -global using MASA.Contrib.BasicAbility.Dcc.Tests.Internal.Model; -global using MASA.Utils.Caching.DistributedMemory; -global using MASA.Utils.Caching.DistributedMemory.Interfaces; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System.Text.Json; diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json b/test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json deleted file mode 100644 index 144652eea..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/appsettings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "DccOptions": { - "ManageServiceAddress": "http://localhost:6379", - "SubscribeKeyPrefix": "masa.dcc:", - "RedisOptions": { - "Servers": [ - { - "Host": "localhost", - "Port": 8888 - } - ], - "DefaultDatabase": 0, - "Password": "" - } - }, - "AppId": "WebApplication1", - "Environment": "Development", - "Cluster": "Default", - "ConfigObjects": [ - "Brand" - ], - "Sectet": "" -} \ No newline at end of file diff --git a/test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json b/test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json deleted file mode 100644 index 28e953152..000000000 --- a/test/MASA.Contrib.BasicAbility.Dcc.Tests/expandSections.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "DccOptions": { - "ManageServiceAddress": "http://localhost:6379", - "RedisOptions": { - "Servers": [ - { - "Host": "localhost", - "Port": 8888 - } - ], - "DefaultDatabase": 0, - "Password": "" - } - }, - "AppId": "DccTest", - "Environment": "Test", - "Cluster": "Default", - "ConfigObjects": [ - "Test1" - ], - "Sectet": "", - "ExpandSections": [ - { - "AppId": "DccTest2", - "Environment": "Test2", - "Cluster": "Default", - "ConfigObjects": [ - "Test3" - ] - } - ] -} \ No newline at end of file diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs deleted file mode 100644 index 1ab0da584..000000000 --- a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests; - -public class ErrorKafkaOptions : KafkaOptions -{ - [JsonIgnore] - public override string? ParentSection { get; init; } = "Appsettings"; - - public ErrorKafkaOptions() - { - base.Section = "KafkaOptions"; - } -} diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs deleted file mode 100644 index a2f1f13ee..000000000 --- a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests; - -public class KafkaOptions : MasaConfigurationOptions -{ - public string Servers { get; set; } - - public int ConnectionPoolSize { get; set; } - - public override SectionTypes SectionType { get; init; } = SectionTypes.Local; - - public KafkaOptions() - { - base.ParentSection = ""; - } -} diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj deleted file mode 100644 index 0ddf5d6a8..000000000 --- a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - - - diff --git a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs b/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs deleted file mode 100644 index ddfd0182f..000000000 --- a/test/MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using MASA.BuildingBlocks.Configuration; -global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj deleted file mode 100644 index 11cf31006..000000000 --- a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - - - diff --git a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs deleted file mode 100644 index 22de5854b..000000000 --- a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests; - -public class MountSectionRedisOptions : MasaConfigurationOptions -{ - [JsonIgnore] - public override string? ParentSection { get; init; } = "Appsettings"; - - [JsonIgnore] - public override string? Section { get; init; } = null; - - public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; -} diff --git a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs b/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs deleted file mode 100644 index ddfd0182f..000000000 --- a/test/MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using MASA.BuildingBlocks.Configuration; -global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs b/test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs deleted file mode 100644 index 3bbcc3a4d..000000000 --- a/test/MASA.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.Configuration.Tests.Config; - -public class RabbitMqOptions : MasaConfigurationOptions -{ - public string HostName { get; set; } - - public string UserName { get; set; } - - public string Password { get; set; } - - public string VirtualHost { get; set; } - - public string Port { get; set; } - - public override SectionTypes SectionType { get; init; } = SectionTypes.Local; -} diff --git a/test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs b/test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs deleted file mode 100644 index 3fe96cb56..000000000 --- a/test/MASA.Contrib.Configuration.Tests/Config/RedisOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Configuration.Tests.Config; - -public class RedisOptions -{ - public string Ip { get; set; } - - public string Password { get; set; } - - public int Port { get; set; } -} diff --git a/test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs b/test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs deleted file mode 100644 index 39f32fa84..000000000 --- a/test/MASA.Contrib.Configuration.Tests/Config/SystemOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Configuration.Tests.Config; - -public class SystemOptions : MasaConfigurationOptions -{ - [JsonIgnore] - public override string? ParentSection { get; init; } = "Appsettings"; - - [JsonIgnore] - public override string? Section { get; init; } = null; - - public override SectionTypes SectionType { get; init; } = SectionTypes.Local; - - public string? Name { get; set; } -} diff --git a/test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs b/test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs deleted file mode 100644 index fe62c475c..000000000 --- a/test/MASA.Contrib.Configuration.Tests/ConfigurationTest.cs +++ /dev/null @@ -1,266 +0,0 @@ -namespace MASA.Contrib.Configuration.Tests; - -[TestClass] -public class ConfigurationTest -{ - private IConfigurationBuilder _configurationBuilder; - - [TestInitialize] - public void Initialize() - { - _configurationBuilder = new ConfigurationBuilder(); - } - - [TestMethod] - public void TestAddSection() - { - var masaConfigurationBuilder = new MasaConfigurationBuilder(_configurationBuilder); - Assert.ThrowsException(() => masaConfigurationBuilder.AddSection(null!)); - - Assert.ThrowsException(() => masaConfigurationBuilder.AddSection(new ConfigurationBuilder())); - - masaConfigurationBuilder.AddSection( - new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true), "appsettings" - ); - - Assert.IsTrue(masaConfigurationBuilder.GetSectionRelations().Count == 1); - - masaConfigurationBuilder.AddSection( - new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("redis.json", true, true) - ); - Assert.IsTrue(masaConfigurationBuilder.GetSectionRelations().Count == 2); - - Assert.ThrowsException(() => - { - masaConfigurationBuilder.AddSection( - new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("rabbitMq.json", true, true) - ); - }); - } - - [TestMethod] - public void TestAddCustomSection() - { - var builder = WebApplication.CreateBuilder(); - builder.AddMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("redis.json", true, true), "RedisOptions"); - - configurationBuilder.AddSection( - new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" - ); - - configurationBuilder.UseMasaOptions(option => - { - option.Mapping(SectionTypes.Local, ""); - }); - }); - var serviceProvider = builder.Services.BuildServiceProvider(); - var configuration = serviceProvider.GetRequiredService(); - var redisOption = serviceProvider.GetRequiredService>(); - - Assert.IsNotNull(configuration); - Assert.IsNotNull(redisOption); - Assert.IsTrue(redisOption.Value.Ip == "localhost"); - } - - [TestMethod] - public void TestAddMasaConfiguration() - { - var builder = WebApplication.CreateBuilder(); - builder.AddMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("redis.json", true, true) - ); - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" - ); - configurationBuilder.UseMasaOptions(option => - option.Mapping(SectionTypes.Local, "", "") - ); - }); - var serviceProvider = builder.Services.BuildServiceProvider(); - var configuration = serviceProvider.GetRequiredService(); - var redisOption = serviceProvider.GetRequiredService>(); - Assert.IsTrue(configuration["Local:Ip"] == "localhost"); - Assert.IsTrue(redisOption.Value.Ip == "localhost"); - - var rabbitMqOption = serviceProvider.GetRequiredService>(); - Assert.IsTrue(configuration["Local:RabbitMqOptions:UserName"] == "admin"); - Assert.IsTrue(rabbitMqOption.Value.UserName == "admin" && rabbitMqOption.Value.Password == "admin"); - } - - [TestMethod] - public void TestAddMultiMasaConfiguration() - { - var builder = WebApplication.CreateBuilder(); - builder.AddMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("redis.json", true, true) - ); - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" - ); - configurationBuilder.UseMasaOptions(option => - option.Mapping(SectionTypes.Local, "", "") - ); - }).AddMasaConfiguration(); - var serviceProvider = builder.Services.BuildServiceProvider(); - var configuration = serviceProvider.GetRequiredService(); - var redisOption = serviceProvider.GetRequiredService>(); - Assert.IsTrue(configuration["Local:Ip"] == "localhost"); - Assert.IsTrue(redisOption.Value.Ip == "localhost"); - - var rabbitMqOption = serviceProvider.GetRequiredService>(); - Assert.IsTrue(configuration["Local:RabbitMqOptions:UserName"] == "admin"); - Assert.IsTrue(rabbitMqOption.Value.UserName == "admin" && rabbitMqOption.Value.Password == "admin"); - } - - [TestMethod] - public void TestAutoMapSectionError() - { - var builder = WebApplication.CreateBuilder(); - builder.Host.ConfigureAppConfiguration((context, config) => { config.Sources.Clear(); }); - var chainedConfiguration = new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true); - builder.Configuration.AddConfiguration(chainedConfiguration.Build()); - - Assert.ThrowsException(() => - builder.AddMasaConfiguration(configurationBuilder => - { - }, "Appsettings", typeof(ConfigurationTest).Assembly, typeof(KafkaOptions).Assembly)); - } - - [TestMethod] - public void TestAutoMapAndErrorSection() - { - var builder = WebApplication.CreateBuilder(); - Assert.ThrowsException(() => - { - return builder.AddMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("redis.json", true, true) - ); //Mount to the Local section - }, "Appsettings", typeof(ConfigurationTest).Assembly, typeof(MountSectionRedisOptions).Assembly); - }); - } - - [TestMethod] - public void TestRepeatMappting() - { - var builder = WebApplication.CreateBuilder(); - Assert.ThrowsException(() => - { - builder.AddMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("redis.json", true, true) - ); - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(builder.Environment.ContentRootPath) - .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" - ); - configurationBuilder.UseMasaOptions(option => - { - option.Mapping(SectionTypes.Local, "", ""); - option.Mapping(SectionTypes.Local, "", ""); - }); - }); - }); - } - - [TestMethod] - public void TestCreateMasaConfiguration() - { - var services = new ServiceCollection(); - services.CreateMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection( - new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("redis.json", true, true) - ); - configurationBuilder.AddSection( - new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" - ); - configurationBuilder.UseMasaOptions(option => - option.Mapping(SectionTypes.Local, "", "") - ); - }, new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true), "Appsettings"); - IServiceProvider serviceProvider = services.BuildServiceProvider(); - var redisOption = serviceProvider.GetRequiredService>(); - Assert.IsTrue(redisOption.Value.Ip == "localhost"); - } - - [TestMethod] - public void TestNullSection() - { - var services = new ServiceCollection(); - var ex = Assert.ThrowsException(() => services.CreateMasaConfiguration(null)); - Assert.IsTrue(ex.Message == "Please add the section to be loaded"); - } - - [TestMethod] - public void TestConfigurationChange() - { - var builder = WebApplication.CreateBuilder(); - - var rootPath = builder.Environment.ContentRootPath; - builder.AddMasaConfiguration(configurationBuilder => - { - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(rootPath) - .AddJsonFile("redis.json", true, true), "RedisOptions"); - - configurationBuilder.AddSection(new ConfigurationBuilder() - .SetBasePath(rootPath) - .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" - ); - - configurationBuilder.UseMasaOptions(option => - { - option.Mapping(SectionTypes.Local, ""); - }); - }, "Appsettings", typeof(ConfigurationTest).Assembly); - var serviceProvider = builder.Services.BuildServiceProvider(); - var configuration = serviceProvider.GetRequiredService(); - var systemOption = serviceProvider.GetRequiredService>(); - - Assert.IsNotNull(configuration); - Assert.IsNotNull(systemOption); - Assert.IsTrue(systemOption.Value.Name == "MASA TEST"); - - var newRedisOption = systemOption.Value; - newRedisOption.Name = null; - - File.WriteAllText(Path.Combine(rootPath, "appsettings.json"), System.Text.Json.JsonSerializer.Serialize(new { SystemOptions = newRedisOption })); - - Thread.Sleep(2000); - var option = serviceProvider.GetRequiredService>(); - Assert.IsTrue(option.CurrentValue.Name == ""); - } -} diff --git a/test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj b/test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj deleted file mode 100644 index 318fc49f0..000000000 --- a/test/MASA.Contrib.Configuration.Tests/MASA.Contrib.Configuration.Tests.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - Always - - - - Always - - - - Always - - - - diff --git a/test/MASA.Contrib.Configuration.Tests/_Imports.cs b/test/MASA.Contrib.Configuration.Tests/_Imports.cs deleted file mode 100644 index 12d9cc8ba..000000000 --- a/test/MASA.Contrib.Configuration.Tests/_Imports.cs +++ /dev/null @@ -1,10 +0,0 @@ -global using MASA.BuildingBlocks.Configuration; -global using MASA.Contrib.Configuration.ErrorSectionAutoMap.Tests; -global using MASA.Contrib.Configuration.MountErrorSectionAutoMap.Tests; -global using MASA.Contrib.Configuration.Tests.Config; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Options; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.Configuration.Tests/appsettings.json b/test/MASA.Contrib.Configuration.Tests/appsettings.json deleted file mode 100644 index 9c1279ff5..000000000 --- a/test/MASA.Contrib.Configuration.Tests/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "KafkaOptions": { - "Servers": "Kafka Server", - "int": 10 - }, - "SystemOptions": { - "Name": "MASA TEST" - } -} \ No newline at end of file diff --git a/test/MASA.Contrib.Configuration.Tests/rabbitMq.json b/test/MASA.Contrib.Configuration.Tests/rabbitMq.json deleted file mode 100644 index cbff2c19a..000000000 --- a/test/MASA.Contrib.Configuration.Tests/rabbitMq.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "HostName": "localhost", - "UserName": "admin", - "Password": "admin", - "VirtualHost": "/", - "Port": 5672 -} \ No newline at end of file diff --git a/test/MASA.Contrib.Configuration.Tests/redis.json b/test/MASA.Contrib.Configuration.Tests/redis.json deleted file mode 100644 index ebce8f626..000000000 --- a/test/MASA.Contrib.Configuration.Tests/redis.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "Ip": "localhost", - "Password": "", - "Port": 6379 -} \ No newline at end of file diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj deleted file mode 100644 index 9dc08646c..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - - - diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs deleted file mode 100644 index d9363e62d..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/Students.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests; - -public class Students : AggregateRoot -{ - public Students() - { - RegisterTime = DateTime.UtcNow; - } - - public string SerialNumber { get; set; } = default!; - - public string Name { get; set; } - - public int Age { get; set; } - - public DateTime RegisterTime { get; private set; } - - /// - /// Test the case of the joint primary key error, no business value - /// - /// - public override IEnumerable<(string Name, object Value)> GetKeys() - => new List<(string Name, object Value)>() - { - ("SerialNumber", SerialNumber), - ("","") - }; -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs deleted file mode 100644 index d6a2a9e7d..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using MASA.BuildingBlocks.DDD.Domain.Entities; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs deleted file mode 100644 index 45965f525..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests; - -public class Courses : AggregateRoot -{ - public Courses() - { - Id = Guid.NewGuid(); - } - - public Guid Id { get; init; } - - public string Name { get; set; } - - public override IEnumerable<(string Name, object Value)> GetKeys() - => new List<(string Name, object Value)>() - { - ("Names",Name)//Demonstrate that a non-existent key is used as a joint primary key - }; -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj deleted file mode 100644 index 9dc08646c..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - - - diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs deleted file mode 100644 index d6a2a9e7d..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using MASA.BuildingBlocks.DDD.Domain.Entities; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs deleted file mode 100644 index 915381728..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Entities; - -public class User : AggregateRoot -{ - public string Name { get; set; } - - public bool Gender { get; set; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj deleted file mode 100644 index 453ed369c..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs deleted file mode 100644 index 83e3677da..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs +++ /dev/null @@ -1,8 +0,0 @@ -using MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Entities; - -namespace MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Repositories; - -public interface IUserRepository : IRepository -{ - -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs deleted file mode 100644 index 5db641dcf..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using MASA.BuildingBlocks.DDD.Domain.Entities; -global using MASA.BuildingBlocks.DDD.Domain.Repositories; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs deleted file mode 100644 index f95e10bc4..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/Hobbies.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests; - -public class Hobbies : AggregateRoot -{ - public string Name { get; private set; } - - private Hobbies() - { - Id = Guid.NewGuid(); - } - - public Hobbies(string name) : this() - { - Name = name; - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj deleted file mode 100644 index 9f97c3017..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - - - diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs deleted file mode 100644 index d6a2a9e7d..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using MASA.BuildingBlocks.DDD.Domain.Entities; diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs deleted file mode 100644 index 1778f8154..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/BaseRepositoryTest.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests; - -[TestClass] -public class BaseRepositoryTest : TestBase -{ - private IServiceCollection _services = default!; - private Assembly[] _assemblies; - private Mock _uoW; - private Mock _dispatcherOptions = default!; - - [TestInitialize] - public void Initialize() - { - _services = new ServiceCollection(); - _assemblies = new Assembly[1] - { - typeof(BaseRepositoryTest).Assembly - }; - _uoW = new(); - _dispatcherOptions = new(); - _dispatcherOptions.Setup(options => options.Services).Returns(() => _services); - } - - [TestMethod] - public void TestNullServices() - { - Assert.ThrowsException(() => - { - _dispatcherOptions.Setup(options => options.Services).Returns(() => null!); - var options = _dispatcherOptions.Object.UseRepository(); - }); - } - - [TestMethod] - public void TestUseCustomRepositoryAndNotImplementation() - { - Mock uoW = new(); - _services.AddScoped(serviceProvider => uoW.Object); - - Assert.ThrowsException(() - => _dispatcherOptions.Object.UseRepository(typeof(TestBase).Assembly, typeof(IUserRepository).Assembly) - ); - } - - [TestMethod] - public void TestNullUnitOfWork() - { - var ex = Assert.ThrowsException(() => - { - _dispatcherOptions.Object.UseRepository(_assemblies); - }); - Assert.IsTrue(ex.Message == "Please add UoW first."); - } - - [TestMethod] - public void TestNullAssembly() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - - Assert.ThrowsException(() => - { - _dispatcherOptions.Object.UseRepository(null!); - }); - } - - [TestMethod] - public void TestAddMultRepository() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - _dispatcherOptions.Object.UseRepository(_assemblies).UseRepository(); - - var serviceProvider = _services.BuildServiceProvider(); - var repository = serviceProvider.GetServices>(); - Assert.IsTrue(repository.Count() == 1); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs deleted file mode 100644 index 27f677f52..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Address.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; - -public class Address : ValueObject -{ - public string Street { get; set; } - - public string City { get; set; } - - public string State { get; set; } - - public string Country { get; set; } - - public string ZipCode { get; set; } - - protected override IEnumerable GetEqualityValues() - { - yield return Street; - yield return City; - yield return State; - yield return Country; - yield return ZipCode; - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs deleted file mode 100644 index 7e252a102..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; - -public class OrderItem : Entity -{ - public Guid OrderId { get; set; } - - public int ProductId { get; set; } - - public string ProductName { get; set; } - - public decimal UnitPrice { get; set; } - - public int Units { get; set; } - - public string PictureUrl { get; set; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs deleted file mode 100644 index 09fed6f3c..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; - -public class Orders : AuditAggregateRoot -{ - public int OrderNumber { get; set; } - - public DateTime OrderDate { get; private set; } - - public string OrderStatus { get; private set; } - - public string Description { get; set; } = default!; - - public List OrderItems { get; set; } - - public Orders() - { - this.OrderDate = DateTime.UtcNow; - this.OrderItems = new(); - this.OrderStatus = "Submitted"; - } - - public Orders(int id) : this() - { - base.Id = id; - } - - /// - /// Joint primary key, when this method does not exist, the primary key is Id - /// - /// - public override IEnumerable<(string Name, object Value)> GetKeys() - { - return new List<(string Name, object value)> - { - ("Id", Id), - ("OrderNumber", OrderNumber) - }; - } -} - diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs deleted file mode 100644 index 03d6adba4..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Repositories; - -public interface IOrderRepository : IRepository -{ - Task AddAsync(Orders order); -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs deleted file mode 100644 index 31e12e8cd..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure; - -public class CustomDbContext : DbContext -{ - public DbSet Orders { get; set; } - - public DbSet Students { get; set; } - - public DbSet Courses { get; set; } - - public DbSet Hobbies { get; set; } - - public CustomDbContext(DbContextOptions options) : base(options) { } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity( - entityTypeBuilder => - { - entityTypeBuilder.HasKey("SerialNumber"); - }); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs deleted file mode 100644 index 717b8527f..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure.Repositories; - -public class OrderRepository : Repository, IOrderRepository -{ - public OrderRepository(CustomDbContext context, IUnitOfWork unitOfWork) : base(context, unitOfWork) - { - } - - public async Task AddAsync(Orders order) - { - try - { - var transaction = base.Transaction; - await base.AddAsync(order, default); - await base.SaveChangesAsync(); - await base.CommitAsync(); - } - catch (Exception) - { - await base.RollbackAsync(); - } - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj deleted file mode 100644 index 9f271c9aa..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/MASA.Contrib.DDD.Domain.Repository.EF.Tests.csproj +++ /dev/null @@ -1,31 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs deleted file mode 100644 index dba5d0c7e..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/RepositoryTest.cs +++ /dev/null @@ -1,273 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests; - -[TestClass] -public class RepositoryTest : TestBase -{ - private IServiceCollection _services = default!; - private Assembly[] _assemblies; - private Mock _uoW; - private Mock _dispatcherOptions = default!; - - [TestInitialize] - public void Initialize() - { - _services = new ServiceCollection(); - _assemblies = new Assembly[1] - { - typeof(BaseRepositoryTest).Assembly - }; - _uoW = new(); - _uoW.Setup(uoW => uoW.UseTransaction).Returns(true); - _dispatcherOptions = new(); - _dispatcherOptions.Setup(options => options.Services).Returns(() => _services); - - } - - [TestMethod] - public async Task TestAsync() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - _dispatcherOptions.Object.UseRepository(_assemblies); - - var serviceProvider = _services.BuildServiceProvider(); - - _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => - { - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - dbContext.SaveChanges(); - }); - _uoW.Setup(u => u.CommitAsync(default)).Verifiable(); - var orders = new List() - { - new Orders(1) - { - OrderNumber = 9999999, - Description = "Apple", - }, - new Orders(2) - { - OrderNumber = 9999999, - Description = "Apple2", - } - }; - - var repository = serviceProvider.GetRequiredService>(); - await repository.AddRangeAsync(orders); - await repository.UnitOfWork.SaveChangesAsync(); - - var orderList = await repository.GetListAsync(order => order.OrderNumber == 9999999, default); - Assert.IsNotNull(orderList); - Assert.IsTrue(orderList.Count() == 2); - - Assert.IsTrue((await repository.GetListAsync(order => order.Description == "Apple", default)).Count() == 1); - Assert.IsTrue(await repository.GetCountAsync(order => order.Description == "Apple", default) == 1); - - var huaweiOrder = await repository.FindAsync(order => order.Description == "Apple2"); - huaweiOrder!.Description = "HuaWei"; - huaweiOrder.OrderNumber = 9999998; - await repository.UnitOfWork.SaveChangesAsync(default); - - Assert.IsTrue((await repository.GetListAsync(order => order.Description == "Apple", default)).Count() == 1); - Assert.IsTrue(await repository.GetCountAsync(order => order.Description == "HuaWei", default) == 1); - - await repository.AddAsync(new Orders(3) - { - OrderNumber = 9999997, - Description = "Google" - }); - await repository.AddAsync(new Orders(4) - { - OrderNumber = 9999996, - Description = "Microsoft" - }); - - await repository.RemoveAsync(order => order.Description == "Apple", default); - await repository.UnitOfWork.SaveChangesAsync(default); - - var list = await repository.GetPaginatedListAsync(0, 10, null, default); - - Assert.IsTrue(list.Count == 3); - Assert.IsTrue(list[0].Description == "HuaWei"); - Assert.IsTrue(list[1].Description == "Google"); - Assert.IsTrue(list[2].Description == "Microsoft"); - - list = await repository.GetPaginatedListAsync(1, 10, null, default); - Assert.IsTrue(list.Count == 2); - Assert.IsTrue(list[0].Description == "Google"); - Assert.IsTrue(list[1].Description == "Microsoft"); - - list = await repository.GetPaginatedListAsync(order => order.Description != "Google", 0, 10, null, default); - Assert.IsTrue(list.Count == 2); - Assert.IsTrue(list[0].Description == "HuaWei"); - - var count = await repository.GetCountAsync(default); - Assert.IsTrue(count == 3); - - var huaWei = await repository.FindAsync(huaweiOrder.Id, huaweiOrder.OrderNumber); - await repository.RemoveAsync(huaWei!, default); - - await repository.UnitOfWork.SaveChangesAsync(default); - Assert.IsTrue(await repository.GetCountAsync(default) == 2); - - var remainingOrders = await repository.GetListAsync(default); - await repository.RemoveRangeAsync(remainingOrders); - await repository.UnitOfWork.SaveChangesAsync(default); - - Assert.IsTrue(await repository.GetCountAsync(default) == 0); - } - - [TestMethod] - public async Task TestTranscationFailedAsync() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - _dispatcherOptions.Object.UseRepository(_assemblies); - - var serviceProvider = _services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - dbContext.Database.BeginTransaction(); - - _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => - { - dbContext.SaveChanges(); - }); - _uoW.Setup(u => u.CommitAsync(default)).Callback(() => - { - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.CurrentTransaction!.Commit(); - }); - _uoW.Setup(u => u.RollbackAsync(default)).Callback(() => - { - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.CurrentTransaction!.RollbackAsync(); - }); - var repository = serviceProvider.GetRequiredService(); - - var order = new Orders() - { - OrderNumber = 1, - }; - await repository.AddAsync(order); - Assert.IsTrue(await repository.GetCountAsync(default) == 0); - } - - [TestMethod] - public async Task TestTranscationSucceededAsync() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - _dispatcherOptions.Object.UseRepository(_assemblies); - - var serviceProvider = _services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - - _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => - { - dbContext.SaveChanges(); - }); - _uoW.Setup(u => u.CommitAsync(default)).Callback(() => - { - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.CurrentTransaction!.Commit(); - }); - _uoW.Setup(u => u.RollbackAsync(default)).Callback(() => - { - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.CurrentTransaction!.RollbackAsync(); - }); - var repository = serviceProvider.GetRequiredService(); - - var order = new Orders(1) - { - OrderNumber = 1, - Description = "Apple" - }; - await repository.AddAsync(order); - Assert.IsTrue(await repository.GetCountAsync(default) == 1); - } - - [TestMethod] - public async Task TestUpdateAsync() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); - _dispatcherOptions.Object.UseRepository(_assemblies); - - var serviceProvider = _services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - - _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => - { - dbContext.SaveChanges(); - }); - var repository = serviceProvider.GetRequiredService(); - - var order = new Orders(1) - { - OrderNumber = 1, - Description = "Apple" - }; - await repository.AddAsync(order, default); - await repository.UnitOfWork.SaveChangesAsync(default); - dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; - - order = await repository.FindAsync(order => order.Description == "Apple"); - order!.Description = "Apple Company"; - await repository.UnitOfWork.SaveChangesAsync(); - - order = await repository.FindAsync(order => order.Description == "Apple"); - Assert.IsNotNull(order); - - await repository.UpdateAsync(order, default); - await repository.UnitOfWork.SaveChangesAsync(); - dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; - Assert.IsTrue(await repository.GetCountAsync(default) == 1); - - order = await repository.FindAsync(order => order.Description == "Apple"); - Assert.IsNotNull(order); - - order.Description = "Apple Company"; - await repository.UpdateRangeAsync(new List() { order }, default); - await repository.UnitOfWork.SaveChangesAsync(); - - dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; - - order = await repository.FindAsync(order => order.Description == "Apple"); - Assert.IsNull(order); - } - - [TestMethod] - public void TestCompositeKeys() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - Assert.ThrowsException(() => - { - _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Students).Assembly); - }); - } - - [TestMethod] - public void TestErrorCompositeKeys() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - Assert.ThrowsException(() => - { - _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Courses).Assembly); - }); - } - - [TestMethod] - public void TestPrivateEntity() - { - _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); - _services.AddDbContext(options => options.UseSqlite(_connection)); - _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Hobbies).Assembly); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs deleted file mode 100644 index 574d3fdd8..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/TestBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Repository.EF.Tests; - -public class TestBase : IDisposable -{ - protected readonly SqliteConnection _connection; - - protected TestBase() - { - _connection = new SqliteConnection("DataSource=:memory:"); - _connection.Open(); - } - - public void Dispose() - { - _connection.Close(); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs deleted file mode 100644 index 0d939826d..000000000 --- a/test/MASA.Contrib.DDD.Domain.Repository.EF.Tests/_Imports.cs +++ /dev/null @@ -1,23 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.DDD.Domain.Entities; -global using MASA.BuildingBlocks.DDD.Domain.Entities.Auditing; -global using MASA.BuildingBlocks.DDD.Domain.Repositories; -global using MASA.BuildingBlocks.DDD.Domain.Values; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeys.Tests; -global using MASA.Contrib.DDD.Domain.Repository.EF.CombinedKeysNoFind.Tests; -global using MASA.Contrib.DDD.Domain.Repository.EF.CustomRepository.Tests.Repositories; -global using MASA.Contrib.DDD.Domain.Repository.EF.Entity.Tests; -global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Entities; -global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Domain.Repositories; -global using MASA.Contrib.DDD.Domain.Repository.EF.Tests.Infrastructure; -global using Microsoft.Data.Sqlite; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System; -global using System.Collections.Generic; -global using System.Linq; -global using System.Reflection; - diff --git a/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs b/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs deleted file mode 100644 index 9365bd391..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/DomainEventBusTest.cs +++ /dev/null @@ -1,349 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests; - -[TestClass] -public class DomainEventBusTest -{ - private Assembly[] _defaultAssemblies = default!; - private IServiceCollection _services = default!; - private Mock _eventBus = default!; - private Mock _integrationEventBus = default!; - private Mock _uoW = default!; - private IOptions _dispatcherOptions = default!; - - [TestInitialize] - public void Initialize() - { - _defaultAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - _services = new ServiceCollection(); - _eventBus = new(); - _integrationEventBus = new(); - _uoW = new(); - _dispatcherOptions = Options.Create(new DispatcherOptions(new ServiceCollection())); - } - - [TestMethod] - public void TestGetAllEventTypes() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - var eventTypes = assemblies.SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)); - _eventBus.Setup(eventBus => eventBus.GetAllEventTypes()).Returns(() => eventTypes); - _dispatcherOptions.Value.Assemblies = _defaultAssemblies; - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); - - Assert.IsTrue(domainEventBus.GetAllEventTypes().Count() == eventTypes.Count(), ""); - } - - [TestMethod] - public async Task TestPublishDomainEventAsync() - { - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); - _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); - - var domainEvent = new PaymentSucceededDomainEvent(new Random().Next(10000, 1000000).ToString()); - await domainEventBus.PublishAsync(domainEvent); - - _eventBus.Verify(eventBus => eventBus.PublishAsync(domainEvent), Times.Once, "PublishAsync is executed multiple times"); - _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(domainEvent), Times.Never, "integrationEventBus should not be executed"); - Assert.IsTrue(domainEvent.UnitOfWork!.Equals(_uoW.Object)); - } - - [TestMethod] - public async Task TestPublishIntegrationDomainEventAsync() - { - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); - _integrationEventBus.Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())).Verifiable(); - var integrationDomainEvent = new PaymentFailedIntegrationDomainEvent() - { - OrderId = new Random().Next(10000, 1000000).ToString() - }; - await domainEventBus.PublishAsync(integrationDomainEvent); - - _eventBus.Verify(eventBus => eventBus.PublishAsync(integrationDomainEvent), Times.Never, "eventBus should not be executed"); - _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Once, " PublishAsync is executed multiple times"); - } - - [TestMethod] - public async Task TestPublishDomainCommandAsync() - { - _uoW.Setup(u => u.CommitAsync(default)).Verifiable(); - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); - _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())) - .Callback((domainEvent) => - { - Mock> userRepository = new(); - var user = new Users() - { - Name = "Jim" - }; - userRepository.Setup(repository => repository.AddAsync(It.IsAny(), CancellationToken.None)).Verifiable(); - domainEvent.UnitOfWork!.CommitAsync(); - }); - - var @command = new CreateProductDomainCommand() - { - Name = "Phone" - }; - await domainEventBus.PublishAsync(@command); - - _eventBus.Verify(eventBus => eventBus.PublishAsync(@command), Times.Once, "PublishAsync is executed multiple times"); - _uoW.Verify(u => u.CommitAsync(default), Times.Once); - } - - [TestMethod] - public void TestAddMultDomainEventBusAsync() - { - _services.AddScoped(serviceProvider => _eventBus.Object); - _services.AddScoped(serviceProvider => _integrationEventBus.Object); - _services.AddScoped(serviceProvider => _uoW.Object); - - _services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }).AddDomainEventBus(); - var serviceProvider = _services.BuildServiceProvider(); - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - Assert.IsTrue(serviceProvider.GetServices>().Count() == 1); - } - - [TestMethod] - public void TestNotUseEventBus() - { - var ex = Assert.ThrowsException(() - => _services.AddDomainEventBus() - ); - Assert.IsTrue(ex.Message == "Please add EventBus first."); - } - - [TestMethod] - public void TestNotUseUnitOfWork() - { - var eventBus = new Mock(); - _services.AddScoped(serviceProvider => eventBus.Object); - - var ex = Assert.ThrowsException(() - => _services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }) - ); - Assert.IsTrue(ex.Message == "Please add UoW first."); - } - - [TestMethod] - public void TestNotUseIntegrationEventBus() - { - var services = new ServiceCollection(); - - var eventBus = new Mock(); - services.AddScoped(serviceProvider => eventBus.Object); - - var uoW = new Mock(); - services.AddScoped(serviceProvider => uoW.Object); - - var ex = Assert.ThrowsException(() - => services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }) - ); - Assert.IsTrue(ex.Message == "Please add IntegrationEventBus first."); - } - - [TestMethod] - public void TestNullAssembly() - { - Assert.ThrowsException(() => _dispatcherOptions.Value.Assemblies = null!); - } - - [TestMethod] - public void TestNotRepository() - { - var services = new ServiceCollection(); - - var eventBus = new Mock(); - services.AddScoped(serviceProvider => eventBus.Object); - - var uoW = new Mock(); - services.AddScoped(serviceProvider => uoW.Object); - - var integrationEventBus = new Mock(); - services.AddScoped(serviceProvider => integrationEventBus.Object); - - Assert.ThrowsException(() => - { - services.AddDomainEventBus(options => - { - options.Assemblies = new Assembly[1] { typeof(Users).Assembly }; - }); - }); - } - - [TestMethod] - public void TestUserRepository() - { - var services = new ServiceCollection(); - - var eventBus = new Mock(); - services.AddScoped(serviceProvider => eventBus.Object); - - var uoW = new Mock(); - services.AddScoped(serviceProvider => uoW.Object); - - var integrationEventBus = new Mock(); - services.AddScoped(serviceProvider => integrationEventBus.Object); - - Mock> repository = new(); - services.AddScoped(serviceProvider => repository.Object); - services.AddDomainEventBus(options => - { - options.Assemblies = new Assembly[2] { typeof(Users).Assembly, typeof(DomainEventBusTest).Assembly }; - }); - } - - [TestMethod] - public async Task TestPublishQueueAsync() - { - var domainEvent = new PaymentSucceededDomainEvent("ef5f84db-76e4-4c79-9815-99a1543b6589"); - var integrationDomainEvent = new PaymentFailedIntegrationDomainEvent { OrderId = "d65c1a0c-6e44-40ce-9737-738fa1dcdab4" }; - - _eventBus - .Setup(eventBus => eventBus.PublishAsync(It.IsAny())) - .Callback(() => - { - _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Never, "Sent in the wrong order"); - }); - - _integrationEventBus - .Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())) - .Callback(() => - { - _eventBus.Verify(eventBus => eventBus.PublishAsync((IDomainEvent)domainEvent), Times.Once, "Sent in the wrong order"); - }); - - var uoW = new Mock(); - uoW.Setup(u => u.CommitAsync(default)).Verifiable(); - - var options = Options.Create(new DispatcherOptions(_services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); - - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, uoW.Object, options); - - await domainEventBus.Enqueue(domainEvent); - await domainEventBus.Enqueue(integrationDomainEvent); - - await domainEventBus.PublishQueueAsync(); - - _eventBus.Verify(eventBus => eventBus.PublishAsync((IDomainEvent)domainEvent), Times.Once, "Sent in the wrong order"); - _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Never, "Sent in the wrong order"); - } - - [TestMethod] - public async Task TestPublishDomainQueryAsync() - { - var services = new ServiceCollection(); - - var eventBus = new Mock(); - eventBus.Setup(e => e.PublishAsync(It.IsAny())) - .Callback(query => - { - if (query.ProductId == "2f8d4c3c-1736-4e56-a188-f865da6a63d1") - query.Result = "apple"; - }); - var integrationEventBus = new Mock(); - var uoW = new Mock(); - uoW.Setup(u => u.CommitAsync(default)).Verifiable(); - - var options = Options.Create(new DispatcherOptions(services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); - - var domainEventBus = new DomainEventBus(eventBus.Object, integrationEventBus.Object, uoW.Object, options); - - var query = new ProductItemDomainQuery() { ProductId = "2f8d4c3c-1736-4e56-a188-f865da6a63d1" }; - - await domainEventBus.PublishAsync(query); - Assert.IsTrue(query.Result == "apple"); - } - - [TestMethod] - public async Task TestCommitAsync() - { - var services = new ServiceCollection(); - - _uoW.Setup(uow => uow.CommitAsync(CancellationToken.None)).Verifiable(); - Mock> options = new(); - - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, options.Object); - await domainEventBus.CommitAsync(CancellationToken.None); - - _uoW.Verify(u => u.CommitAsync(default), Times.Once, "CommitAsync must be called only once"); - } - - [TestMethod] - public void TestParameterInitialization() - { - var id = Guid.NewGuid(); - var createTime = DateTime.UtcNow; - - var domainCommand = new DomainCommand(); - Assert.IsTrue(domainCommand.Id != default); - Assert.IsTrue(domainCommand.CreationTime != default && domainCommand.CreationTime >= createTime); - - domainCommand = new DomainCommand(id, createTime); - Assert.IsTrue(domainCommand.Id == id); - Assert.IsTrue(domainCommand.CreationTime == createTime); - - var domainEvent = new DomainEvent(); - Assert.IsTrue(domainEvent.Id != default); - Assert.IsTrue(domainEvent.CreationTime != default && domainEvent.CreationTime >= createTime); - - domainEvent = new DomainEvent(id, createTime); - Assert.IsTrue(domainEvent.Id == id); - Assert.IsTrue(domainEvent.CreationTime == createTime); - - var domainQuery = new ProductItemDomainQuery() - { - ProductId = Guid.NewGuid().ToString() - }; - Assert.IsTrue(domainQuery.Id != default); - Assert.IsTrue(domainQuery.CreationTime != default && domainQuery.CreationTime >= createTime); - } - - [TestMethod] - public void TestDomainQueryUnitOfWork() - { - var domainQuery = new ProductItemDomainQuery() - { - ProductId = Guid.NewGuid().ToString() - }; - Assert.ThrowsException(() => - { - domainQuery.UnitOfWork = _uoW.Object; - }); - Assert.IsNull(domainQuery.UnitOfWork); - } - - [TestMethod] - public async Task TestDomainServiceAsync() - { - _integrationEventBus.Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())).Verifiable(); - - _services.AddDomainEventBus(options => - { - options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }; - options.Services.AddScoped(serviceProvider => _eventBus.Object); - options.Services.AddScoped(serviceProvider => _integrationEventBus.Object); - options.Services.AddScoped(serviceProvider => _uoW.Object); - }); - var serviceProvider = _services.BuildServiceProvider(); - - var userDomainService = serviceProvider.GetRequiredService(); - var domainIntegrationEvent = new RegisterUserSucceededDomainIntegrationEvent() { Account = "Tom" }; - await userDomainService.RegisterUserSucceededAsync(domainIntegrationEvent); - - _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(domainIntegrationEvent), Times.Once); - } - - [TestMethod] - public async Task TestPublishEvent() - { - var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); - _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); - - var @event = new ForgetPasswordEvent() - { - Account = "Tom" - }; - await domainEventBus.PublishAsync(@event); - _eventBus.Verify(eventBus => eventBus.PublishAsync(@event), Times.Once); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs b/test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs deleted file mode 100644 index 1aeb39d9a..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/DomainIntegrationEventBusTest.cs +++ /dev/null @@ -1,49 +0,0 @@ -๏ปฟnamespace MASA.Contrib.DDD.Domain.Tests; - -[TestClass] -public class DomainIntegrationEventBus -{ - private Assembly[] _defaultAssemblies = default!; - private IServiceCollection _services = default!; - private Mock _integrationEventBus = default!; - private Mock _uoW = default!; - private IOptions _dispatcherOptions = default!; - - [TestInitialize] - public void Initialize() - { - _defaultAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - _services = new ServiceCollection(); - _integrationEventBus = new(); - _integrationEventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); - _uoW = new(); - _dispatcherOptions = Options.Create(new DispatcherOptions(new ServiceCollection())); - } - - [TestMethod] - public async Task PublishQueueAsync() - { - _services.AddEventBus(opt => - { - opt.Assemblies = _defaultAssemblies; - }); - var serviceProvider = _services.BuildServiceProvider(); - var eventBus = serviceProvider.GetRequiredService(); - var payment = new - { - orderId = Guid.NewGuid(), - money = 100, - payTime = DateTime.UtcNow - }; - var domainEventBus = new DomainEventBus(eventBus, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); - - var domainEvent = new PaymentSucceededDomainEvent(payment.orderId.ToString()); - await domainEventBus.Enqueue(domainEvent); - - var integraionDomainEvent = new PaymentSucceededIntegraionDomainEvent(payment.orderId.ToString(), payment.money, payment.payTime); - await domainEventBus.Enqueue(integraionDomainEvent); - await domainEventBus.PublishQueueAsync(); - Assert.IsTrue(domainEvent.Result); - _integrationEventBus.Verify(eventBus => eventBus.PublishAsync(It.IsAny()), Times.Once); - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/CreateProductDomainCommand.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/CreateProductDomainCommand.cs deleted file mode 100644 index 6d8bac9c8..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/CreateProductDomainCommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Events; - -public record CreateProductDomainCommand : DomainCommand -{ - public string Name { get; set; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs deleted file mode 100644 index 7e8bdcf94..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/ForgetPasswordEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Events; - -public class ForgetPasswordEvent : IEvent -{ - public Guid Id { get; init; } = Guid.NewGuid(); - - public DateTime CreationTime { get; init; } = DateTime.UtcNow; - - public string Account { get; set; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs deleted file mode 100644 index 70d3a5098..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Events; - -public record PaymentFailedIntegrationDomainEvent : IntegrationDomainEvent -{ - public string OrderId { get; set; } - - public override string Topic { get; set; } = nameof(PaymentFailedIntegrationDomainEvent); -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs deleted file mode 100644 index 5b38d08b8..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededDomainEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Events; - -public record PaymentSucceededDomainEvent(string OrderId) : DomainEvent -{ - public bool Result { get; set; } = false; -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs deleted file mode 100644 index 44719cdbf..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -๏ปฟnamespace MASA.Contrib.DDD.Domain.Tests.Events; - -public record PaymentSucceededIntegraionDomainEvent(string OrderId, decimal Money, DateTime PayTime) : IntegrationDomainEvent -{ - public override string Topic { get; set; } = nameof(PaymentSucceededIntegraionDomainEvent); -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/ProductItemDomainQuery.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/ProductItemDomainQuery.cs deleted file mode 100644 index 208af8c71..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/ProductItemDomainQuery.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Events; - -public record ProductItemDomainQuery : DomainQuery -{ - public string ProductId { get; set; } - - public override string Result { get; set; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs b/test/MASA.Contrib.DDD.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs deleted file mode 100644 index fe48da8a5..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Events; - -public record RegisterUserSucceededDomainIntegrationEvent : IntegrationDomainEvent -{ - public override string Topic { get; set; } = nameof(RegisterUserSucceededDomainIntegrationEvent); - - public string Account { get; init; } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs b/test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs deleted file mode 100644 index 2127a6ea6..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs +++ /dev/null @@ -1,19 +0,0 @@ -๏ปฟnamespace MASA.Contrib.DDD.Domain.Tests.Handlers; - -public class PaymentSucceededDomainEventHandller -{ - private readonly ILogger? _logger; - - public PaymentSucceededDomainEventHandller(ILogger? logger = null) - { - _logger = logger; - } - - [EventHandler] - public Task PaymentSucceeded(PaymentSucceededDomainEvent domainEvent) - { - _logger?.LogInformation("PaymentSucceeded: OrderId: {OrderId}", domainEvent.OrderId); - domainEvent.Result = true; - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj b/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj deleted file mode 100644 index 3753ec12d..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/MASA.Contrib.DDD.Domain.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs b/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs deleted file mode 100644 index 52065a304..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/Services/UserDomainService.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.DDD.Domain.Tests.Services; - -public class UserDomainService : DomainService -{ - public UserDomainService(IDomainEventBus eventBus) : base(eventBus) - { - } - - public async Task RegisterUserSucceededAsync(RegisterUserSucceededDomainIntegrationEvent domainIntegrationEvent) - { - // TODO Simulate a successful message for registered users - - await EventBus.PublishAsync(domainIntegrationEvent); - return "succeed"; - } -} diff --git a/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs b/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs deleted file mode 100644 index e1a0ca7d6..000000000 --- a/test/MASA.Contrib.DDD.Domain.Tests/_Imports.cs +++ /dev/null @@ -1,16 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.DDD.Domain.Events; -global using MASA.BuildingBlocks.DDD.Domain.Repositories; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.Contrib.DDD.Domain.Events; -global using MASA.Contrib.DDD.Domain.Tests.Events; -global using MASA.Contrib.DDD.Domain.Tests.Services; -global using MASA.Contribs.DDD.Domain.Entities.Tests; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Options; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System.Reflection; -global using MASA.Contrib.Dispatcher.Events; -global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs deleted file mode 100644 index 2b6039096..000000000 --- a/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; - -public class Courses : AggregateRoot -{ - public Courses() - { - Id = Guid.NewGuid(); - IsDeleted = false; - } - - public string Name { get; set; } - - public bool IsDeleted { get; set; } -} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs deleted file mode 100644 index 968c5de7c..000000000 --- a/test/MASA.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; - -public class Students : AuditAggregateRoot -{ - public Students() - { - Id = Guid.NewGuid(); - RegisterTime = DateTime.UtcNow; - } - - public string Name { get; set; } - - public int Age { get; set; } - - public DateTime RegisterTime { get; private set; } -} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs deleted file mode 100644 index 46299964b..000000000 --- a/test/MASA.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; - -namespace MASA.Contrib.Data.Contracts.EF.Tests.Infrastructure; - -public class CustomDbContext : MasaDbContext -{ - public DbSet Students { get; set; } - - public DbSet Courses { get; set; } - - public CustomDbContext(MasaDbContextOptions options) : base(options) { } -} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj b/test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj deleted file mode 100644 index 02aa8883f..000000000 --- a/test/MASA.Contrib.Data.Contracts.EF.Tests/MASA.Contrib.Data.Contracts.EF.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs deleted file mode 100644 index 0a427c46e..000000000 --- a/test/MASA.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs +++ /dev/null @@ -1,117 +0,0 @@ -using MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; -using MASA.Contrib.Data.Contracts.EF.Tests.Infrastructure; - -namespace MASA.Contrib.Data.Contracts.EF.Tests; - -[TestClass] -public class SoftDeleteTest : IDisposable -{ - protected readonly SqliteConnection _connection; - - public SoftDeleteTest() - { - _connection = new SqliteConnection("DataSource=:memory:"); - _connection.Open(); - } - - public void Dispose() - { - _connection.Close(); - } - - [TestMethod] - public void UseNotUseUoW() - { - var services = new ServiceCollection(); - services.AddMasaDbContext(option => - { - option.UseSqlite(_connection); - Assert.ThrowsException(() => option.UseSoftDelete(services), "Please add UoW first."); - }); - } - - [TestMethod] - public void TestUseSoftDelete() - { - Mock uoW = new(); - uoW.Setup(u => u.Transaction).Verifiable(); - var services = new ServiceCollection(); - services.AddScoped(serviceProvider => uoW.Object); - services.AddMasaDbContext(option => - { - option.UseSqlite(_connection); - option.UseSoftDelete(services); - }); - - var serviceProvider = services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - - dbContext.Set().Add(new Students() - { - Name = "Tom", - Age = 18 - }); - dbContext.SaveChanges(); - Assert.IsTrue(dbContext.Students.Count() == 1); - uoW.Verify(u => u.Transaction, Times.Never); - - var student = dbContext.Students.FirstOrDefault(s => s.Name == "Tom"); - Assert.IsNotNull(student); - dbContext.Set().Remove(student); - dbContext.SaveChanges(); - - Assert.IsTrue(!dbContext.Students.Any()); - - student.IsDeleted = false; - dbContext.SaveChanges(); - Assert.IsTrue(dbContext.Students.Count() == 1); - - uoW = new(); - uoW.Setup(u => u.Transaction).Verifiable(); - uoW.Setup(u => u.UseTransaction).Returns(() => true); - services = new ServiceCollection(); - services.AddScoped(serviceProvider => uoW.Object); - services.AddMasaDbContext(option => - { - option.UseSqlite(_connection); - option.UseSoftDelete(services); - }); - - serviceProvider = services.BuildServiceProvider(); - dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - - dbContext.Set().Add(new Courses() - { - Name = "Chinese" - }); - dbContext.SaveChanges(); - Assert.IsTrue(dbContext.Courses.Count() == 1); - uoW.Verify(u => u.Transaction, Times.Never); - - var course = dbContext.Set().FirstOrDefault(c => c.Name == "Chinese"); - Assert.IsNotNull(course); - dbContext.Set().Remove(course); - dbContext.SaveChanges(); - Assert.IsTrue(!dbContext.Courses.Any()); - - course.IsDeleted = false; - dbContext.SaveChanges(); - Assert.IsTrue(!dbContext.Courses.Any()); - } - - [TestMethod] - public void TestUseMultiSoftDelete() - { - Mock uoW = new(); - uoW.Setup(u => u.Transaction).Verifiable(); - var services = new ServiceCollection(); - services.AddScoped(serviceProvider => uoW.Object); - services.AddMasaDbContext(option => - { - option.UseSqlite(_connection); - option.UseSoftDelete(services).UseSoftDelete(services); - }); - } -} diff --git a/test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs b/test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs deleted file mode 100644 index 13d3abe9d..000000000 --- a/test/MASA.Contrib.Data.Contracts.EF.Tests/_Imports.cs +++ /dev/null @@ -1,10 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.DDD.Domain.Entities; -global using MASA.BuildingBlocks.DDD.Domain.Entities.Auditing; -global using MASA.Contrib.Data.Contracts.EF.Tests.Domain.Entities; -global using MASA.Utils.Data.EntityFrameworkCore; -global using Microsoft.Data.Sqlite; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs deleted file mode 100644 index ce3b81dda..000000000 --- a/test/MASA.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace MASA.Contrib.Data.UoW.EF.Tests; - -public class CustomerDbContext : MasaDbContext -{ - public CustomerDbContext() - { - - } - - public CustomerDbContext(MasaDbContextOptions options) : base(options) { } - - public DbSet User { get; set; } - - protected override void OnModelCreatingExecuting(ModelBuilder builder) - { - builder.Entity(ConfigureUserEntry); - } - - void ConfigureUserEntry(EntityTypeBuilder builder) - { - builder.ToTable("Users"); - - builder.HasKey(e => e.Id); - - builder.Property(e => e.Id) - .IsRequired(); - - builder.Property(e => e.Name) - .HasMaxLength(6) - .IsRequired(); - } -} - -public class Users -{ - public Guid Id { get; private set; } - - public string Name { get; set; } = default!; - - public Users() - { - this.Id = Guid.NewGuid(); - } -} diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj b/test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj deleted file mode 100644 index 78360345b..000000000 --- a/test/MASA.Contrib.Data.UoW.EF.Tests/MASA.Contrib.Data.UoW.EF.Tests.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs deleted file mode 100644 index 878c8982e..000000000 --- a/test/MASA.Contrib.Data.UoW.EF.Tests/TestBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MASA.Contrib.Data.UoW.EF.Tests; - -public class TestBase : IDisposable -{ - protected readonly SqliteConnection _connection; - - protected TestBase() - { - _connection = new SqliteConnection("DataSource=:memory:"); - _connection.Open(); - } - - public void Dispose() - { - _connection.Close(); - } -} diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs deleted file mode 100644 index be9a58037..000000000 --- a/test/MASA.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ /dev/null @@ -1,171 +0,0 @@ -namespace MASA.Contrib.Data.UoW.EF.Tests; - -[TestClass] -public class TestUnitOfWork : TestBase -{ - private Mock _options; - - [TestInitialize] - public void Initialize() - { - _options = new(); - _options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); - } - - [TestMethod] - public void TestAddUoWAndNullServices() - { - var options = new Mock(); - Assert.ThrowsException(() => options.Object.UseUoW()); - } - - [TestMethod] - public void TestAddUoW() - { - _options.Object.UseUoW(); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - Assert.ThrowsException(() - => serviceProvider.GetRequiredService() - ); - } - - [TestMethod] - public void TestAddUoWAndUseSqlLite() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - Assert.IsNotNull(serviceProvider.GetRequiredService()); - } - - [TestMethod] - public void TestAddMultUoW() - { - _options.Object - .UseUoW(options => options.UseSqlite(_connection)) - .UseUoW(options => options.UseSqlite(_connection)); - - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - } - - [TestMethod] - public void TestTransaction() - { - Mock uoW = new(); - Assert.IsTrue(new Transaction(uoW.Object).UnitOfWork!.Equals(uoW.Object)); - } - - [TestMethod] - public async Task TestSaveChangesAsync() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - Mock customerDbContext = new(); - customerDbContext.Setup(dbContext => dbContext.SaveChangesAsync(default)).Verifiable(); - var uoW = new UnitOfWork(customerDbContext.Object, null); - await uoW.SaveChangesAsync(default); - customerDbContext.Verify(dbContext => dbContext.SaveChangesAsync(default), Times.Once); - } - - [TestMethod] - public async Task TestUseTranscationAsync() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var uoW = serviceProvider.GetRequiredService(); - - var transaction = uoW.Transaction; - Users user = new Users() - { - Name = Guid.NewGuid().ToString() - }; - dbContext.Add(user); - await uoW.SaveChangesAsync(); - await uoW.RollbackAsync(); - - Assert.IsTrue(dbContext.User.ToList().Count() == 0); - } - - [TestMethod] - public async Task TestNotUseTranscationAsync() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var uoW = new UnitOfWork(dbContext, null); - - Users user = new Users() - { - Name = Guid.NewGuid().ToString() - }; - dbContext.Add(user); - await uoW.SaveChangesAsync(); - await Assert.ThrowsExceptionAsync(async () => await uoW.RollbackAsync()); - } - - [TestMethod] - public async Task TestNotTransactionCommitAsync() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var uoW = new UnitOfWork(dbContext, null); - await Assert.ThrowsExceptionAsync(async () => await uoW.CommitAsync()); - } - - [TestMethod] - public async Task TestCommitAsync() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var uoW = new UnitOfWork(dbContext, null); - var user = new Users() - { - Name = "Tom" - }; - var transcation = uoW.Transaction; - dbContext.User.Add(user); - await uoW.SaveChangesAsync(); - await uoW.CommitAsync(); - - Assert.IsTrue(dbContext.User.ToList().Count == 1); - } - - [TestMethod] - public async Task TestOpenRollbackAsync() - { - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var uoW = serviceProvider.GetRequiredService(); - var user = new Users(); - var transcation = uoW.Transaction; - dbContext.User.Add(user); - await uoW.CommitAsync(); - - Assert.IsTrue(!await dbContext.User.AnyAsync()); - } - - [TestMethod] - public async Task TestAddLoggerAndOpenRollbackAsync() - { - _options.Object.Services.AddLogging(); - _options.Object.UseUoW(options => options.UseSqlite(_connection)); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); - dbContext.Database.EnsureCreated(); - var uoW = serviceProvider.GetRequiredService(); - var user = new Users(); - var transcation = uoW.Transaction; - dbContext.User.Add(user); - await uoW.CommitAsync(); - - Assert.IsTrue(!await dbContext.User.AnyAsync()); - } -} diff --git a/test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs b/test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs deleted file mode 100644 index 1af16327c..000000000 --- a/test/MASA.Contrib.Data.UoW.EF.Tests/_Imports.cs +++ /dev/null @@ -1,11 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Utils.Data.EntityFrameworkCore; -global using Microsoft.Data.Sqlite; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.Metadata.Builders; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System; -global using System.Threading.Tasks; diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs deleted file mode 100644 index 383306d65..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests; - -[SimpleJob(RunStrategy.ColdStart, RuntimeMoniker.Net60, targetCount: 100)] -[MinColumn, MaxColumn, MeanColumn, MedianColumn] -public class Benchmarks -{ - private RegisterUserEvent _userEvent; - private ForgetPasswordEvent _forgetPasswordEvent; - private IServiceProvider _serviceProvider; - private IEventBus _eventBus; - - [GlobalSetup] - public void GlobalSetup() - { - IServiceCollection services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.ClearProviders()); - services.AddEventBus(); - _serviceProvider = services.BuildServiceProvider(); - _eventBus = _serviceProvider.GetRequiredService(); - _userEvent = new RegisterUserEvent() - { - Name = "tom", - PhoneNumber = "18888888888" - }; - _forgetPasswordEvent = new ForgetPasswordEvent() - { - Name = "lisa", - PhoneNumber = "19999999999" - }; - } - - [Benchmark] - public async Task Direct() - { - var _couponHandler = new CouponHandler(_serviceProvider); - await _couponHandler.SendCoupon(_userEvent); - } - - [Benchmark] - public async Task LambdaTree() - { - await _eventBus.PublishAsync(_userEvent); - } - - [Benchmark] - public async Task SendForgetPasseordByDirect() - { - var emailNoticeHandler = new NoticeEmailHandler(_serviceProvider); - var smsNoticeHandler = new NoticeSmsHandler(_serviceProvider); - var sendCouponHandler = new SendCouponHandler(_serviceProvider); - await emailNoticeHandler.HandleAsync(_forgetPasswordEvent); - await smsNoticeHandler.HandleAsync(_forgetPasswordEvent); - await sendCouponHandler.HandleAsync(_forgetPasswordEvent); - } - - [Benchmark] - public async Task SendForgetPasseordByInterfaces() - { - await _eventBus.PublishAsync(_forgetPasswordEvent); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs deleted file mode 100644 index 2d22773d0..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; - -public class CouponHandler -{ - private readonly ILogger? _logger; - - public CouponHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); - - [EventHandler(Order = 10, FailureLevels = FailureLevels.ThrowAndCancel)] - public Task SendCoupon(RegisterUserEvent @event) - { - _logger?.LogInformation("------Send Coupon------"); - - var num = new Random().Next(1, 3); - if (num % 2 == 0) - { - //throw new Exception("Failed to send coupons"); - } - - return Task.CompletedTask; - } - - [EventHandler(Order = 20, FailureLevels = FailureLevels.Ignore)] - public Task SendNotice(RegisterUserEvent @event) - { - _logger?.LogInformation("------Send Coupon------"); - - var num = new Random().Next(1, 3); - if (num % 2 == 0) - { - //throw new Exception("Failed to send coupons"); - } - - return Task.CompletedTask; - } - - [EventHandler(Order = 10, RetryTimes = 5, IsCancel = true)] - public Task CancelSendCoupon(RegisterUserEvent @event) - { - _logger?.LogInformation("------Cancel Send Coupon------"); - - var num = new Random().Next(1, 3); - if (num % 2 == 0) - { - //throw new Exception("Failed to cancel send coupons"); - } - - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs deleted file mode 100644 index 001c4d74d..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs +++ /dev/null @@ -1,63 +0,0 @@ -๏ปฟusing MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; - -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; - -public class SendCouponHandler : ISagaEventHandler -{ - private readonly ILogger? _logger; - - public SendCouponHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); - - [EventHandler(Order = 10)] - public Task HandleAsync(ForgetPasswordEvent @event) - { - _logger?.LogInformation("------Send Coupon------"); - return Task.CompletedTask; - } - - public Task CancelAsync(ForgetPasswordEvent @event) - { - _logger?.LogInformation("------Cancel Coupon------"); - return Task.CompletedTask; - } -} - -public class NoticeSmsHandler : ISagaEventHandler -{ - private readonly ILogger? _logger; - - public NoticeSmsHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); - - [EventHandler(Order = 20)] - public Task HandleAsync(ForgetPasswordEvent @event) - { - _logger?.LogInformation("------Send Sms Notice------"); - return Task.CompletedTask; - } - - public Task CancelAsync(ForgetPasswordEvent @event) - { - _logger?.LogInformation("------Cancel Sms Notice------"); - return Task.CompletedTask; - } -} - -public class NoticeEmailHandler : ISagaEventHandler -{ - private readonly ILogger? _logger; - - public NoticeEmailHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); - - [EventHandler(Order = 30)] - public Task HandleAsync(ForgetPasswordEvent @event) - { - _logger?.LogInformation("------Send Email Notice------"); - return Task.CompletedTask; - } - - public Task CancelAsync(ForgetPasswordEvent @event) - { - _logger?.LogInformation("------Cancel Email Notice------"); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs deleted file mode 100644 index dc9e2fbb7..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; - -public record ForgetPasswordEvent : Event -{ - public string Name { get; set; } - - public string PhoneNumber { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs deleted file mode 100644 index 30ba9d578..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; - -public record RegisterUserEvent : Event -{ - public string Name { get; set; } - - public string PhoneNumber { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs deleted file mode 100644 index fd07fde6c..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs +++ /dev/null @@ -1,15 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Middleware; - -public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent -{ - private readonly ILogger>? _logger; - public LoggingMiddleware(ILogger>? logger = null) => _logger = logger; - - public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) - { - var eventType = @event.GetType(); - _logger?.LogInformation("----- Handling command {CommandName} ({@Command})", eventType.FullName, @event); - - await next(); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj deleted file mode 100644 index 0dbc15b6a..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - Exe - net6.0 - AnyCPU - false - enable - false - enable - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs deleted file mode 100644 index 0ec5f1cb3..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests; - -class Program -{ - static void Main(string[] args) - { - var config = DefaultConfig.Instance - .AddValidator(ExecutionValidator.FailOnError) - .WithOptions(ConfigOptions.DisableOptimizationsValidator); - BenchmarkRunner.Run(config); - - Console.ReadLine(); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs deleted file mode 100644 index 5276b15ee..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs +++ /dev/null @@ -1,15 +0,0 @@ -global using BenchmarkDotNet.Attributes; -global using BenchmarkDotNet.Configs; -global using BenchmarkDotNet.Engines; -global using BenchmarkDotNet.Jobs; -global using BenchmarkDotNet.Running; -global using BenchmarkDotNet.Validators; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; -global using MASA.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; -global using MASA.Contrib.Dispatcher.Events.Enums; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; -global using System; -global using System.Threading.Tasks; - diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs deleted file mode 100644 index 200e042d0..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.EventHandlers; - -public class AddGoodsHandler -{ - [EventHandler] - public void AddGoods(AddGoodsEvent @event, ILogger? logger) - { - logger?.LogInformation($"add goods log,GoodsId:{@event.GoodsId},GoodsName:{@event.GoodsName},CategoryId:{@event.CategoryId}"); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs deleted file mode 100644 index e4ee6897f..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; - -public record AddGoodsEvent : Event -{ - public string GoodsId { get; set; } - - public string CategoryId { get; set; } - - public string GoodsName { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs deleted file mode 100644 index 61d74bb3c..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; -global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs deleted file mode 100644 index 105ad47fb..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.EventHandlers; - -public class DeleteGoodsHandler -{ - [EventHandler] - public void DeleteGoods() - { - - } -} \ No newline at end of file diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs deleted file mode 100644 index a0d9a873b..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.Events; - -public record DeleteGoodsEvent : Event -{ - public string GoodsId { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/_Imports.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs deleted file mode 100644 index 1681085fe..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.EventHandlers; - -public class AddCatalogHandler -{ - /// - /// The method name of this method can be named according to the actual business - /// but we recommend HandlerAsync or CancelAsync if the business is single - /// - /// - /// - [EventHandler] - public Task HandleAsync(AddCatalogEvent @event) - { - return Task.FromResult("success"); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs deleted file mode 100644 index 6546ca250..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; - -public class AddCatalogEvent -{ - public string GoodsId { get; set; } - - public int Count { get; set; } -} \ No newline at end of file diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs deleted file mode 100644 index 53869939c..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs deleted file mode 100644 index 58b91c70c..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.EventHandlers; - -public class AddBasketHandler -{ - private readonly ILogger? _logger; - public AddBasketHandler(ILogger? logger) => _logger = logger; - - [EventHandler] - public Task AddLog(AddBasketEvent @event) - { - _logger?.LogInformation($"add basket log๏ผšGoogdsId๏ผš{@event.GoodsId}๏ผŒcount๏ผš{@event.Count}"); - return Task.FromResult("success"); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs deleted file mode 100644 index 9c9aea4e3..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.Events; - -public record AddBasketEvent : Event -{ - public string GoodsId { get; set; } - - public int Count { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs deleted file mode 100644 index 65d6bb9f9..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.Events; -global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs deleted file mode 100644 index 02ae7f52a..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.EventHandlers; - -public class UserEventHandler -{ - [EventHandler(IsCancel = true)] - public void BindPhoneNumber(BindPhoneNumberEvent @event) - { - - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs deleted file mode 100644 index a28c44bb8..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; - -public record BindPhoneNumberEvent : Event -{ - public string AccountId { get; set; } - - public string PhoneNumber { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs deleted file mode 100644 index fdc78d0c0..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using MASA.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs deleted file mode 100644 index 1d0dcc2a3..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.EventHandlers; - -public class EditCategoryHandler : ISagaEventHandler -{ - private readonly ILogger? _logger; - public EditCategoryHandler(ILogger? logger = null) => _logger = logger; - - [EventHandler(10)] - public Task CancelAsync(EditCategoryEvent @event) - { - _logger?.LogInformation($"cancel edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); - return Task.CompletedTask; - } - - [EventHandler(20)] - public Task HandleAsync(EditCategoryEvent @event) - { - _logger?.LogInformation($"edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs deleted file mode 100644 index 19dd45a74..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; - -public record EditCategoryEvent : Event -{ - public string CategoryId { get; set; } - - public string CategoryName { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs deleted file mode 100644 index cecf0b334..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; -global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs deleted file mode 100644 index a97d6302a..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.EventHandlers; - -public class OrderStockConfirmedHandler -{ - private readonly ILogger? _logger; - - public OrderStockConfirmedHandler(ILogger? logger = null) => _logger = logger; - - [EventHandler(-10)] - public void AddLog(OrderStockConfirmedEvent @event) - { - _logger?.LogInformation($"add order stock confirmed log,orderId:{@event.OrderId}"); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs deleted file mode 100644 index 28aa594da..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; - -public record OrderStockConfirmedEvent : Event -{ - public string OrderId { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs deleted file mode 100644 index abbead4e1..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs +++ /dev/null @@ -1,2 +0,0 @@ -global using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; -global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs deleted file mode 100644 index ca832f1f9..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.EventHandlers; - -public class EditGoodsHandler : IEventHandler -{ - private readonly ILogger? _logger; - public EditGoodsHandler(ILogger? logger) => _logger = logger; - - [EventHandler(-10)] - public Task HandleAsync(EditGoodsEvent @event) - { - _logger?.LogInformation($"edit goods log๏ผŒGoodsId:{@event.GoodsId},Name:{@event.GoodsName},CategoryId:{@event.CategoryId}"); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs deleted file mode 100644 index 7cb975479..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; - -public record EditGoodsEvent : Event -{ - public string GoodsId { get; set; } - - public string CategoryId { get; set; } - - public string GoodsName { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj deleted file mode 100644 index 65fe8ba17..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs deleted file mode 100644 index 2fa58832a..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; -global using Microsoft.Extensions.Logging; diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs deleted file mode 100644 index c9b51ff42..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests; - -[TestClass] -public class AssemblyResolutionTests -{ - [TestMethod] - public void TestResolveEventBus() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - services.AddEventBus(); - var serviceProvider = services.BuildServiceProvider(); - var eventBus = serviceProvider.GetService(); - Assert.IsNotNull(eventBus, "Event bus injection failed"); - Assert.IsNotNull(eventBus.GetAllEventTypes()); - } - - [TestMethod] - public void TestAddDefaultAssembly() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = AppDomain.CurrentDomain.GetAssemblies()); - } - - [TestMethod] - public void TestAddNullAssembly() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - Assert.ThrowsException(() => - { - services.AddEventBus(options => options.Assemblies = null!); - }); - } - - [TestMethod] - public void TestAddEmptyAssembly() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - Assert.ThrowsException(() => - { - services.AddEventBus(options => options.Assemblies = new Assembly[0]); - }); - } - - [TestMethod] - public void TestEventBusByAddNullAssembly() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - Assert.ThrowsException(() => - { - services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = null!); - }); - } - - [TestMethod] - public void TestEventBusByAddEmptyAssembly() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - Assert.ThrowsException(() => - { - services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = new Assembly[0]); - }); - } - - [TestMethod] - public void TestEventBus() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - services.AddTestEventBus(ServiceLifetime.Scoped); - } - - [TestMethod] - public void TestUseEventBus() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - var options = new DispatcherOptions(services); - options.UseEventBus(); - - var eventBus = services.BuildServiceProvider().GetService(); - Assert.IsNotNull(eventBus); - } - - [TestMethod] - public void TestAddMultEventBus() - { - var services = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - var options = new DispatcherOptions(services); - options.UseEventBus().UseEventBus(); - - Assert.IsTrue(services.BuildServiceProvider().GetServices().Count() == 1); - - var services2 = new ServiceCollection(); - services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - services2.AddTestEventBus(ServiceLifetime.Scoped) - .AddTestEventBus(ServiceLifetime.Scoped); - var serviceProvider = services.BuildServiceProvider(); - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - } - - [TestMethod] - public void TestUseEventBusAndNullServices() - { - var options = new DispatcherOptions(null!); - Assert.ThrowsException(() => options.UseEventBus()); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs deleted file mode 100644 index 6a28ce658..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/ChoreTest.cs +++ /dev/null @@ -1,102 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests; - -[TestClass] -public class ChoreTest : TestBase -{ - private readonly IEventBus _eventBus; - public ChoreTest() - { - _eventBus = _serviceProvider.GetRequiredService(); - } - - [DataTestMethod] - [DataRow("jordan", "19999999999", 1, "A very ordinary boy who likes code")] - [DataRow("tom", "18888888888", 0, "A girl who likes to dance")] - public async Task TestNotHandler(string account, string phone, int gender, string abstracts) - { - AddUserEvent @event = new AddUserEvent() - { - Account = account, - Phone = phone, - Gender = gender == 1, - Abstract = abstracts - }; - await Assert.ThrowsExceptionAsync(async () => - { - await _eventBus.PublishAsync(@event); - }); - } - - [TestMethod] - public void TestDispatchHandlerConstructor() - { - var dispatchHandler = new EventHandlerAttribute(); - Assert.IsTrue(dispatchHandler.Order.Equals(int.MaxValue)); - - dispatchHandler = new EventHandlerAttribute(1); - Assert.IsTrue(dispatchHandler.Order.Equals(1)); - - dispatchHandler = new EventHandlerAttribute(1, true); - Assert.IsTrue(dispatchHandler.Order.Equals(1)); - Assert.IsTrue(dispatchHandler.EnableRetry.Equals(true)); - - dispatchHandler = new EventHandlerAttribute(2, FailureLevels.Ignore); - Assert.IsTrue(dispatchHandler.Order.Equals(2)); - Assert.IsTrue(dispatchHandler.EnableRetry.Equals(false)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.Ignore)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); - - dispatchHandler = new EventHandlerAttribute(10, true, false); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.EnableRetry.Equals(true)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(3)); - - dispatchHandler = new EventHandlerAttribute(10, true, false, 5); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.EnableRetry.Equals(true)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(5)); - - dispatchHandler = new EventHandlerAttribute(10, false, false, 5); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.EnableRetry.Equals(false)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); - - dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, true); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(3)); - - dispatchHandler = new EventHandlerAttribute(10, FailureLevels.Ignore, true, false); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.Ignore)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(3)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); - - dispatchHandler = new EventHandlerAttribute(10, FailureLevels.Ignore, false, true); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.Ignore)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(true)); - - dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, true, 10); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(10)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); - - dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, false, 10); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); - - dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, true, 5, true); - Assert.IsTrue(dispatchHandler.Order.Equals(10)); - Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); - Assert.IsTrue(dispatchHandler.RetryTimes.Equals(5)); - Assert.IsTrue(dispatchHandler.IsCancel.Equals(true)); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs deleted file mode 100644 index 202fdc8bd..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class ChangePasswordEventHandler : ISagaEventHandler -{ - private readonly ILogger? _logger; - public ChangePasswordEventHandler(ILogger? logger=null) => _logger = logger; - - [EventHandler(10, FailureLevels.ThrowAndCancel)] - public Task HandleAsync(ChangePasswordEvent @event) - { - if (@event.Content.Contains("@")) - { - throw new ArgumentException("Invalid content parameter"); - } - return Task.CompletedTask; - } - - public Task CancelAsync(ChangePasswordEvent @event) - { - if (@event.Account.Equals("mark")) - { - throw new ArgumentException("System error, please try again later"); - } - _logger?.LogInformation("cancel success"); - return Task.CompletedTask; - } - - - [EventHandler(0, FailureLevels.Ignore, IsCancel = true)] - public Task AddCancelLogs(ChangePasswordEvent @event) - { - if (@event.Account.Equals("roller")) - { - throw new ArgumentException("System error, please try again later"); - } - _logger?.LogInformation("cancel success"); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs deleted file mode 100644 index e20b79ae2..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class MarketingEventHandler -{ - [EventHandler(10, true, false)] - public void Discount(ComputeEvent computeEvent) - { - var discountRate = 0.7m; - computeEvent.DiscountAmount = computeEvent.Amount * (1 - discountRate); - computeEvent.PayAmount = computeEvent.Amount * discountRate; - } - - [EventHandler(20)] - public void FullReduction(ComputeEvent computeEvent) - { - var discounts = 0; - if (computeEvent.PayAmount > 200) - { - discounts = 50; - computeEvent.DiscountAmount += discounts; - } - computeEvent.PayAmount -= discounts; - } -} \ No newline at end of file diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs deleted file mode 100644 index 5f403b17a..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class OrderPaymentSucceededEventHandler -{ - private readonly ILogger _logger; - public OrderPaymentSucceededEventHandler(ILogger logger) => _logger = logger; - - [EventHandler(10, FailureLevels.Ignore)] - public void AddTradeRecords(OrderPaymentSucceededEvent @event) - { - _logger.LogInformation("Order paid successfully, add transaction record"); - if (@event.OrderId.Length > 10) - { - throw new NotSupportedException("Wrong order number"); - } - } - - [EventHandler(10, FailureLevels = FailureLevels.Ignore, IsCancel = true)] - public void Cancel(OrderPaymentSucceededEvent @event) - { - _logger.LogInformation("Order paid successfully, rollback transaction record"); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs deleted file mode 100644 index c3400a461..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class ShipOrderEventHandler : ISagaEventHandler -{ - private int ExecCount { get; set; } - - private readonly ILogger? _logger; - - public ShipOrderEventHandler(ILogger? logger = null) - { - _logger = logger; - ExecCount = 0; - } - - [EventHandler(10, FailureLevels.ThrowAndCancel, true)] - public Task HandleAsync(ShipOrderEvent @event) - { - ExecCount++; - if (ExecCount - 1 <= 1) - { - throw new Exception("try again"); - } - - _logger?.LogInformation("update express information"); - if (@event.OrderId.Length > 8) - { - @event.Message = "the delivery failure"; - throw new Exception("the delivery failure"); - } - @event.Message = "the delivery success"; - return Task.CompletedTask; - } - - [EventHandler(10, false, true)] - public Task CancelAsync(ShipOrderEvent @event) - { - @event.Message = "the delivery failed, rolling back success"; - _logger?.LogInformation("the delivery failed, rolling back success"); - return Task.CompletedTask; - } -} - -public class ShipOrderAndNoticeHandler : IEventHandler -{ - private readonly ILogger? _logger; - public ShipOrderAndNoticeHandler(ILogger? logger = null) => _logger = logger; - - [EventHandler(20)] - public Task HandleAsync(ShipOrderEvent @event) - { - @event.Message = "the delivery and notice success"; - _logger?.LogInformation("order delivered successfully"); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs deleted file mode 100644 index e7230ce56..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class ShoppingCardEventHandler -{ - private readonly ILogger _logger; - public ShoppingCardEventHandler(ILogger logger) => _logger = logger; - - [EventHandler(FailureLevels = FailureLevels.Ignore)] - public void AddShoppingCard(AddShoppingCartEvent @event) - { - _logger.LogInformation($"add shopping card log๏ผŒGoodsId:{@event.GoodsId},Count๏ผš{@event.Count}"); - throw new ArgumentException(nameof(@event)); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs deleted file mode 100644 index 0b41eb4b8..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class TransferEventHandler : ISagaEventHandler -{ - private readonly List _blackAccount = new List() { "roller", "thomas" }; - - private readonly ILogger? _logger; - private readonly IEventBus _eventBus; - - public TransferEventHandler(IEventBus eventBus, ILogger? logger = null) - { - _logger = logger; - _eventBus = eventBus; - } - - [EventHandler(EnableRetry = true, RetryTimes = 3)] - public Task HandleAsync(TransferEvent @event) - { - if (_blackAccount.Contains(@event.Account)) - { - throw new NotSupportedException("System error, please try again later"); - } - _logger?.LogInformation("deduct account balance {event}", @event.ToString()); - return Task.CompletedTask; - } - - [EventHandler(EnableRetry = true, RetryTimes = 3)] - public Task CancelAsync(TransferEvent @event) - { - if (@event.Price > 1000000) - { - throw new NotSupportedException("Large transfer returns are not supported."); - } - else - { - return Task.CompletedTask; - } - } - - [EventHandler] - public async Task DeductionMoneyHandler(DeductionMoneyEvent @event) - { - // TODO: The simulated deduction is successful - - IncreaseMoneyEvent increaseMoneyEvent = new IncreaseMoneyEvent() - { - Account = @event.PayeeAccount, - TransferAccount = @event.Account, - Money = @event.Money - }; - await _eventBus.PublishAsync(increaseMoneyEvent); - } - - [EventHandler] - public Task IncreaseMoneyHandler(IncreaseMoneyEvent @event) - { - // TODO: Succeeded in simulated increase - return Task.CompletedTask; - } -} - -public class ReceiveTransferHandler -{ - private readonly List _blackAccount = new List() { "clark", "evan" }; - - private readonly ILogger _logger; - - public ReceiveTransferHandler(ILogger logger) => _logger = logger; - - [EventHandler(EnableRetry = true, RetryTimes = 3, FailureLevels = FailureLevels.ThrowAndCancel)] - public Task HandleAsync(TransferEvent @event) - { - if (_blackAccount.Contains(@event.OptAccount)) - { - throw new NotSupportedException("System error, please try again later"); - } - _logger.LogInformation("add opt account balance"); - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs deleted file mode 100644 index 462174144..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.EventHandlers; - -public class UserEventHandler : IEventHandler -{ - [EventHandler(10, true, 1, FailureLevels = FailureLevels.ThrowAndCancel)] - public Task HandleAsync(EditUserEvent @event) - { - throw new NotSupportedException("users cannot be modified"); - } - - /// - /// This CancelHandler is not the same as EventHandler in Saga mode, so a different order can be used - /// - /// - /// - [EventHandler(20, true, 1, FailureLevels = FailureLevels.Ignore, IsCancel = true)] - public Task CancelAsync(EditUserEvent @event) - { - throw new NotSupportedException("edit users do not support cancellation"); - } - - [EventHandler(10, true)] - public void ForgotPassword(ForgotPasswordEvent @event) - { - throw new Exception("Password retrieval is not supported"); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs deleted file mode 100644 index e789540dc..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record AddShoppingCartEvent : Event -{ - public string GoodsId { get; set; } - - public int Count { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs deleted file mode 100644 index b47139bc1..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs +++ /dev/null @@ -1,15 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -/// -/// Just event, not handler, so let's see what happens when you publish an event -/// -public record AddUserEvent : Event -{ - public string Account { get; set; } - - public string Phone { get; set; } - - public bool Gender { get; set; } - - public string Abstract { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs deleted file mode 100644 index 5ace89196..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public class ChangePasswordEvent : IEvent -{ - public string Account { get; set; } - - public string Content { get; set; } - - public Guid Id => Guid.NewGuid(); - - public DateTime CreationTime => DateTime.UtcNow; -} \ No newline at end of file diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs deleted file mode 100644 index 63d73fc73..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs +++ /dev/null @@ -1,26 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record ComputeEvent : Event -{ - /// - /// the unit price - /// - public decimal Price { get; set; } - - public int Count { get; set; } - - /// - /// original price - /// - public decimal Amount => Price * Count; - - /// - /// preferential price - /// - public decimal DiscountAmount { get; set; } - - /// - /// actual amount paid - /// - public decimal PayAmount { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs deleted file mode 100644 index 9623b3fc3..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record DeductionMoneyEvent : Event, ITransaction -{ - public IUnitOfWork? UnitOfWork { get; set; } - - public string Account { get; set; } - - public string PayeeAccount { get; set; } - - public decimal Money { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs deleted file mode 100644 index ebf99eb62..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record EditUserEvent : Event -{ - public string UserId { get; set; } - - public string UserName { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs deleted file mode 100644 index 72b46b4d7..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record ForgotPasswordEvent() : Event -{ - public string Account { get; set; } - - public string Email { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs deleted file mode 100644 index ecbe79664..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record IncreaseMoneyEvent : Event, ITransaction -{ - public IUnitOfWork? UnitOfWork { get; set; } - - public string Account { get; set; } - - public string TransferAccount { get; set; } - - public decimal Money { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs deleted file mode 100644 index c715ad3ce..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public class OrderPaymentFailedIntegrationEvent : IIntegrationEvent -{ - public Guid Id { get; init; } - - public DateTime CreationTime { get; init; } - - public string Topic { get; set; } = nameof(OrderPaymentFailedIntegrationEvent); - - public IUnitOfWork? UnitOfWork { get; set; } - - public string OrderId { get; set; } - - public OrderPaymentFailedIntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public OrderPaymentFailedIntegrationEvent(Guid id, DateTime creationTime) - { - this.Id = id; - this.CreationTime = creationTime; - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs deleted file mode 100644 index 574bf2fd1..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record OrderPaymentSucceededEvent : Event -{ - public string OrderId { get; set; } - - public long Timespan { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs deleted file mode 100644 index a708ae7d5..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record ShipOrderEvent : Event -{ - public string OrderId { get; set; } - - public string OrderState { get; set; } - - public string Message { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs deleted file mode 100644 index 8eebfbd8c..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -๏ปฟnamespace MASA.Contrib.Dispatcher.Events.Tests.Events; - -public record TransferEvent : Event -{ - public string Account { get; set; } - - public string OptAccount { get; set; } - - public decimal Price { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs deleted file mode 100644 index 0a6824ab8..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs +++ /dev/null @@ -1,278 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests; - -[TestClass] -public class FeaturesTest : TestBase -{ - private readonly IEventBus _eventBus; - public FeaturesTest() : base() - { - _eventBus = _serviceProvider.GetRequiredService(); - } - - [TestMethod] - public async Task TestMethodsReturnType() - { - await Assert.ThrowsExceptionAsync(async () => - { - try - { - ResetMemoryEventBus(typeof(AddBasketEvent).Assembly); - } - catch (Exception) - { - ResetMemoryEventBus(typeof(FeaturesTest).Assembly); - throw; - } - await Task.CompletedTask; - }); - } - - [TestMethod] - public async Task TestNotImplementationIEvent() - { - await Assert.ThrowsExceptionAsync(async () => - { - try - { - ResetMemoryEventBus(typeof(AddCatalogEvent).Assembly); - } - catch (Exception) - { - ResetMemoryEventBus(typeof(FeaturesTest).Assembly); - throw; - } - await Task.CompletedTask; - }); - } - - [TestMethod] - public async Task TestMultiParameter() - { - await Assert.ThrowsExceptionAsync(async () => - { - try - { - ResetMemoryEventBus(typeof(AddGoodsEvent).Assembly); - } - catch (Exception) - { - ResetMemoryEventBus(typeof(FeaturesTest).Assembly); - throw; - } - await Task.CompletedTask; - }); - } - - [TestMethod] - public async Task TestCorrectEventBus() - { - AddShoppingCartEvent @event = new AddShoppingCartEvent() - { - GoodsId = Guid.NewGuid().ToString(), - Count = 1 - }; - await _eventBus.PublishAsync(@event); - } - - [TestMethod] - public async Task TestNullEvent() - { - AddShoppingCartEvent? @event = null; - await Assert.ThrowsExceptionAsync(async () => await _eventBus.PublishAsync(@event!)); - } - - [DataTestMethod] - [DataRow("50", 2, "30", "70")] - [DataRow("60", 5, "140", "160")] - public async Task TestMultiHandler(string price, int count, string discountAmount, string payAmount) - { - ComputeEvent @event = new ComputeEvent() - { - Price = Convert.ToDecimal(price), - Count = count - }; - await _eventBus.PublishAsync(@event); - Assert.IsTrue(@event.DiscountAmount == Convert.ToDecimal(discountAmount) && @event.PayAmount == Convert.ToDecimal(payAmount)); - } - - [TestMethod] - public async Task TestNotParameter() - { - await Assert.ThrowsExceptionAsync(async () => - { - try - { - ResetMemoryEventBus(typeof(DeleteGoodsEvent).Assembly); - } - catch (Exception) - { - ResetMemoryEventBus(typeof(FeaturesTest).Assembly); - throw; - } - await Task.CompletedTask; - }); - } - - [TestMethod] - public async Task TestThrowException() - { - ForgotPasswordEvent @event = new ForgotPasswordEvent() - { - Account = new Random().Next(1000, 9000).ToString(), - Email = new Random().Next(100000, 9000000).ToString() + "@qq.com", - }; - await Assert.ThrowsExceptionAsync(async () => - { - await _eventBus.PublishAsync(@event); - }); - } - - [TestMethod] - public Task TestOrderLessThenZero() - { - Assert.ThrowsException(() => - { - try - { - ResetMemoryEventBus(typeof(OrderStockConfirmedEvent).Assembly); - } - catch (ArgumentOutOfRangeException) - { - try - { - ResetMemoryEventBus(typeof(FeaturesTest).Assembly); - } - catch (Exception) - { - - } - throw; - } - }); - return Task.CompletedTask; - } - - [TestMethod] - public Task TestOnlyCancelHandler() - { - Assert.ThrowsException(() => - { - try - { - ResetMemoryEventBus(typeof(OnlyCancelHandler.Tests.Events.BindPhoneNumberEvent).Assembly); - } - catch (NotSupportedException) - { - ResetMemoryEventBus(typeof(FeaturesTest).Assembly); - throw; - } - }); - return Task.CompletedTask; - } - - [TestMethod] - public async Task TestHandlerIsIgnore() - { - var @event = new OrderPaymentSucceededEvent() - { - OrderId = "123456789012", - Timespan = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds - }; - await _eventBus.PublishAsync(@event); - } - - [TestMethod] - public async Task TestPublishIntegrationEvent() - { - var @event = new OrderPaymentFailedIntegrationEvent() - { - OrderId = "123456789012", - }; - await Assert.ThrowsExceptionAsync(async () => - { - await _eventBus.PublishAsync(@event); - }); - } - - [TestMethod] - public async Task TestPublishIntegrationEventAndUseUoW() - { - base.ResetMemoryEventBus(services => - { - var unitOfWork = new Mock(); - services.AddScoped(serviceProvider => unitOfWork.Object); - return services; - }, true, typeof(AssemblyResolutionTests).Assembly); - var @event = new OrderPaymentFailedIntegrationEvent() - { - OrderId = "123456789012", - }; - await Assert.ThrowsExceptionAsync(async () => - { - await _services.BuildServiceProvider().GetRequiredService().PublishAsync(@event); - }); - } - - [TestMethod] - public async Task TestTransferEventAndOpenTransaction() - { - base.ResetMemoryEventBus(services => - { - var uoW = new Mock(); - uoW.Setup(x => x.TransactionHasBegun).Returns(true); - uoW.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); - services.AddScoped(serviceProvider => uoW.Object); - return services; - }, true, typeof(AssemblyResolutionTests).Assembly); - var @event = new DeductionMoneyEvent() - { - Account = "tom", - PayeeAccount = "Jim", - Money = 100 - }; - await _services.BuildServiceProvider().GetRequiredService().PublishAsync(@event); - } - - [TestMethod] - public async Task TestCommitAsync() - { - base.ResetMemoryEventBus(services => - { - return services; - }, true, typeof(AssemblyResolutionTests).Assembly); - var @event = new DeductionMoneyEvent() - { - Account = "tom", - PayeeAccount = "Jim", - Money = 100 - }; - var serviceProvider = _services.BuildServiceProvider(); - var eventBus = serviceProvider.GetRequiredService(); - - await Assert.ThrowsExceptionAsync(async () => await eventBus.CommitAsync(default)); - } - - [TestMethod] - public async Task TestUseUoWCommitAsync() - { - var uoW = new Mock(); - base.ResetMemoryEventBus(services => - { - uoW.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); - services.AddScoped(serviceProvider => uoW.Object); - return services; - }, true, typeof(AssemblyResolutionTests).Assembly); - var @event = new DeductionMoneyEvent() - { - Account = "tom", - PayeeAccount = "Jim", - Money = 100 - }; - var serviceProvider = _services.BuildServiceProvider(); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(@event); - - await eventBus.CommitAsync(default); - uoW.Verify(u => u.CommitAsync(default), Times.Once); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj b/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj deleted file mode 100644 index 3470232d9..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/MASA.Contrib.Dispatcher.Events.Tests.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - net6.0 - false - enable - Full - enable - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs deleted file mode 100644 index e3088c8ab..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests.Middleware; - -public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent -{ - private readonly ILogger>? _logger; - public LoggingMiddleware(ILogger>? logger = null) => _logger = logger; - - public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) - { - var eventType = @event.GetType(); - _logger?.LogInformation("----- Handling command {FullName} ({event})", eventType.FullName, @event); - await next(); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs deleted file mode 100644 index ce8fee8b2..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/SagaTest.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests; - -[TestClass] -public class SagaTest : TestBase -{ - private readonly IEventBus _eventBus; - public SagaTest() : base() - { - _eventBus = _serviceProvider.GetRequiredService(); - } - - [DataTestMethod] - [DataRow("60040012", "success", "the delivery and notice success")] - [DataRow("601454112", "error", "the delivery failed, rolling back success")] - public async Task TestExecuteAbnormalExit(string orderId, string orderState, string result) - { - ShipOrderEvent @event = new ShipOrderEvent() - { - OrderId = orderId, - OrderState = orderState - }; - await _eventBus.PublishAsync(@event); - Assert.IsTrue(@event.Message == result); - } - - [DataTestMethod] - [DataRow("roller", "change password notcices", 0)] - [DataRow("mark", "change password notcices @", 1)] - [DataRow("roller", "change password notcices @", 0)] - [DataRow("jordan", "change password notcices @", 0)] - public async Task TestLastCancelError(string account, string content, int isError) - { - ResetMemoryEventBus(false, null!); - ChangePasswordEvent @event = new ChangePasswordEvent() - { - Account = account, - Content = content - }; - if (isError == 1) - { - await Assert.ThrowsExceptionAsync(async () => - { - await _eventBus.PublishAsync(@event); - }); - } - else - { - await _eventBus.PublishAsync(@event); - } - } - - [TestMethod] - public async Task TestEqualMethodsBySaga() - { - EditUserEvent @event = new EditUserEvent() - { - UserId = new Random().Next(10, 1000000).ToString(), - UserName = "roller" - }; - await _eventBus.PublishAsync(@event); - } - - [DataTestMethod] - [DataRow("smith", "alice", "1000", 0)] - [DataRow("roller", "alice", "1000", 1)] - [DataRow("eddie", "clark", "2000", 0)] - [DataRow("eddie", "clark", "20000000", 1)] - public async Task TestMultiHandlerBySaga(string account, string optAccount, string price, int isError) - { - TransferEvent @event = new TransferEvent() - { - Account = account, - OptAccount = optAccount, - Price = Convert.ToDecimal(price) - }; - if (isError == 1) - { - await Assert.ThrowsExceptionAsync(async () => - { - await _eventBus.PublishAsync(@event); - }); - } - else - { - await _eventBus.PublishAsync(@event); - } - } - - [TestMethod] - public async Task TestMultiOrderBySaga() - { - IEventBus? eventBus = null; - Assert.ThrowsException(() => - { - ResetMemoryEventBus(false, typeof(SagaTest).Assembly, typeof(EditCategoryEvent).Assembly); - eventBus = _serviceProvider.GetRequiredService(); - }); - EditCategoryEvent @event = new EditCategoryEvent() - { - CategoryId = new Random().Next(100, 10000).ToString(), - CategoryName = "Name" - }; - if (eventBus != null) - { - await eventBus.PublishAsync(@event); - } - ResetMemoryEventBus(false, null!); - } - - [TestMethod] - public async Task TestLessThenZeroBySaga() - { - IEventBus? eventBus = null; - Assert.ThrowsException(() => - { - ResetMemoryEventBus(false, typeof(EditGoodsEvent).Assembly); - eventBus = _serviceProvider.GetRequiredService(); - }); - EditGoodsEvent @event = new EditGoodsEvent() - { - GoodsId = new Random().Next(10, 1000).ToString(), - CategoryId = new Random().Next(100, 10000).ToString(), - GoodsName = "Name" - }; - if (eventBus != null) - { - await eventBus.PublishAsync(@event); - } - ResetMemoryEventBus(false, null!); - } -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs deleted file mode 100644 index 551056db7..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/TestBase.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace MASA.Contrib.Dispatcher.Events.Tests; - -[TestClass] -public class TestBase -{ - protected IServiceProvider _serviceProvider { get; private set; } - - protected IServiceCollection _services { get; private set; } - - public TestBase() : this(null) - { - - } - - public TestBase(Func? func = null) => ResetMemoryEventBus(func, false, null); - - protected void ResetMemoryEventBus(Func? func = null, bool isAddLog = true, params Assembly[]? assemblies) - { - _services = new ServiceCollection(); - if (isAddLog) - { - _services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); - } - else - { - _services.AddLogging(loggingBuilder => - { - loggingBuilder.ClearProviders(); - loggingBuilder.Services.RemoveAll(typeof(ILogger<>)); - }); - } - - _services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); - func?.Invoke(_services); - _services = assemblies == null ? _services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = _defaultAssemblies) : _services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = assemblies); - _serviceProvider = _services.BuildServiceProvider(); - } - - private static Assembly[] _defaultAssemblies => new Assembly[1] { typeof(TestBase).Assembly }; - - protected void ResetMemoryEventBus(params Assembly[] assemblies) => ResetMemoryEventBus(null, true, assemblies); - - protected void ResetMemoryEventBus(bool isAddLog, params Assembly[] assemblies) => ResetMemoryEventBus(null, true, assemblies); -} diff --git a/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs deleted file mode 100644 index dda486fce..000000000 --- a/test/MASA.Contrib.Dispatcher.Events.Tests/_Imports.cs +++ /dev/null @@ -1,20 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.CheckMethodsType.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.Enums; -global using MASA.Contrib.Dispatcher.Events.Options; -global using MASA.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.Tests.Events; -global using MASA.Contrib.Dispatcher.Events.Tests.Middleware; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System.Reflection; diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs deleted file mode 100644 index 978d98783..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.Events; - -public record CreateUserEvent : IEvent -{ - public string Name { get; set; } - - public Guid Id { get; set; } - - public DateTime CreationTime { get; set; } - - public CreateUserEvent() - { - this.Id = Guid.NewGuid(); - this.CreationTime = DateTime.UtcNow; - } - - public CreateUserEvent(string name) : this() - { - this.Name = name; - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs deleted file mode 100644 index d1f551f25..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests.Events; - -public record RegisterUserIntegrationEvent : IntegrationEvent -{ - public RegisterUserIntegrationEvent() - { - - } - - public RegisterUserIntegrationEvent(Guid id, DateTime creationTime) : base(id, creationTime) - { - - } - - public string Account { get; set; } - - public string Password { get; set; } - - public override string Topic { get; set; } = nameof(RegisterUserIntegrationEvent); -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs deleted file mode 100644 index 9bd6b68d4..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs +++ /dev/null @@ -1,370 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.Tests; - -[TestClass] -public class IntegrationEventBusTest -{ - private Mock _options; - private Mock> _dispatcherOptions; - private Mock _daprClient; - private Mock> _logger; - private Mock _eventLog; - private Mock> _appConfig; - private Mock _eventBus; - private Mock _uoW; - - [TestInitialize] - public void Initialize() - { - _options = new(); - _options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); - _dispatcherOptions = new(); - _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services)); - _daprClient = new(); - _logger = new(); - _eventLog = new(); - _eventLog.Setup(eventLog => eventLog.SaveEventAsync(It.IsAny(), null!)).Verifiable(); - _eventLog.Setup(eventLog => eventLog.MarkEventAsInProgressAsync(It.IsAny())).Verifiable(); - _eventLog.Setup(eventLog => eventLog.MarkEventAsPublishedAsync(It.IsAny())).Verifiable(); - _eventLog.Setup(eventLog => eventLog.MarkEventAsFailedAsync(It.IsAny())).Verifiable(); - _appConfig = new(); - _appConfig.Setup(appConfig => appConfig.CurrentValue).Returns(() => new AppConfig() - { - AppId = "Test" - }); - _eventBus = new(); - _uoW = new(); - _uoW.Setup(uoW => uoW.CommitAsync(default)).Verifiable(); - _uoW.Setup(uoW => uoW.Transaction).Returns(() => null!); - } - - [TestMethod] - public void TestDispatcherOption() - { - var services = new ServiceCollection(); - DispatcherOptions options; - - Assert.ThrowsException(() => - { - options = new DispatcherOptions(services) - { - Assemblies = null! - }; - }); - Assert.ThrowsException(() => - { - options = new DispatcherOptions(services) - { - Assemblies = new System.Reflection.Assembly[0] - }; - }); - options = new DispatcherOptions(services) - { - Assemblies = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } - }; - Assert.IsTrue(options.Services.Equals(services)); - var allEventTypes = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsClass && type != typeof(IntegrationEvent) && typeof(IEvent).IsAssignableFrom(type)).ToList(); - Assert.IsTrue(options.AllEventTypes.Count == allEventTypes.Count()); - - } - - [TestMethod] - public void TestAddMultDaprEventBus() - { - _dispatcherOptions.Object.Value.UseDaprEventBus() - .UseDaprEventBus(); - var serviceProvider = _dispatcherOptions.Object.Value.Services.BuildServiceProvider(); - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - } - - [TestMethod] - public void TestAddDaprEventBus() - { - IServiceCollection services = new ServiceCollection(); - services.AddDaprEventBus(); - var serviceProvider = services.BuildServiceProvider(); - var integrationEventBus = serviceProvider.GetRequiredService(); - Assert.IsNotNull(integrationEventBus); - } - - [TestMethod] - public void TestEmptyPubSub() - { - IServiceCollection services = new ServiceCollection(); - Assert.ThrowsException(() => - { - services.AddDaprEventBus(option => - { - option.PubSubName = ""; - }); - }); - } - - [TestMethod] - public void TestAddDaprEventBusAndChangeAssemblies() - { - IServiceCollection services = new ServiceCollection(); - - services.AddDaprEventBus(option => - { - option.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); - option.PubSubName = "pubsub"; - }); - var serviceProvider = services.BuildServiceProvider(); - var integrationEventBus = serviceProvider.GetRequiredService(); - Assert.IsNotNull(integrationEventBus); - } - - [TestMethod] - public void TestAddDaprEventBusAndNullServicesAsync() - { - _options.Setup(option => option.Services).Returns(() => null!); - var ex = Assert.ThrowsException(() => _options.Object.UseDaprEventBus()); - Assert.IsTrue(ex.Message == $"Value cannot be null. (Parameter '{nameof(_options.Object.Services)}')"); - } - - [TestMethod] - public async Task TestPublishIntegrationEventAsync() - { - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - _uoW.Object); - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() - { - Account = "lisa", - Password = "123456" - }; - _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); - await integrationEventBus.PublishAsync(@event); - - _daprClient.Verify(dapr => dapr.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); - } - - [TestMethod] - public async Task TestPublishIntegrationEventAndFailedAsync() - { - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - _uoW.Object); - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() - { - Account = "lisa", - Password = "123456" - }; - _eventLog.Setup(eventLog => eventLog.MarkEventAsPublishedAsync(It.IsAny())).Throws(); - _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); - await integrationEventBus.PublishAsync(@event); - - _eventLog.Verify(eventLog => eventLog.MarkEventAsInProgressAsync(@event.Id), Times.Once); - _daprClient.Verify(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); - _eventLog.Verify(eventLog => eventLog.MarkEventAsPublishedAsync(@event.Id), Times.Once); - _eventLog.Verify(eventLog => eventLog.MarkEventAsFailedAsync(@event.Id), Times.Once); - } - - [TestMethod] - public async Task TestPublishIntegrationEventAndNotUoWAsync() - { - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - _uoW.Object); - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() - { - Account = "lisa", - Password = "123456", - UnitOfWork = _uoW.Object - }; - _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); - await integrationEventBus.PublishAsync(@event); - - _daprClient.Verify(dapr => dapr.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); - } - - [TestMethod] - public async Task TestPublishEventAsync() - { - _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - _uoW.Object); - CreateUserEvent @event = new CreateUserEvent() - { - Name = "Tom" - }; - await integrationEventBus.PublishAsync(@event); - - _eventBus.Verify(eventBus => eventBus.PublishAsync(It.IsAny()), Times.Once); - } - [TestMethod] - public async Task TestPublishEventAndNotEventBusAsync() - { - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - null, - _uoW.Object); - CreateUserEvent @event = new CreateUserEvent() - { - Name = "Tom" - }; - await Assert.ThrowsExceptionAsync(async () => - { - await integrationEventBus.PublishAsync(@event); - }); - } - - [TestMethod] - public async Task TestCommitAsync() - { - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - _uoW.Object); - - await integrationEventBus.CommitAsync(default); - _uoW.Verify(uoW => uoW.CommitAsync(default), Times.Once); - } - - [TestMethod] - public async Task TestNotUseUowCommitAsync() - { - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - null); - - await Assert.ThrowsExceptionAsync(async () => await integrationEventBus.CommitAsync()); - } - - [TestMethod] - public void TestGetAllEventTypes() - { - _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services) - { - Assemblies = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } - }); - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - null, - null); - - Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == _dispatcherOptions.Object.Value.AllEventTypes.Count()); - } - - - [TestMethod] - public void TestUseEventBusGetAllEventTypes() - { - var defaultAssembly = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly }; - _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services) - { - Assemblies = defaultAssembly - }); - var allEventType = defaultAssembly - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) - .ToList(); - _eventBus.Setup(eventBus => eventBus.GetAllEventTypes()).Returns(() => allEventType).Verifiable(); - var integrationEventBus = new IntegrationEventBus( - _dispatcherOptions.Object, - _daprClient.Object, - _eventLog.Object, - _appConfig.Object, - _logger.Object, - _eventBus.Object, - null); - - Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == _dispatcherOptions.Object.Value.AllEventTypes.Count()); - Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == allEventType.Count()); - } - - [TestMethod] - public void TestIntegrationEvent() - { - DateTime date = DateTime.UtcNow; - Guid id = Guid.NewGuid(); - RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() - { - Account = "lisa", - Password = "123456" - }; - Assert.IsTrue(@event.CreationTime > date); - Assert.IsTrue(@event.Id != default(Guid)); - - @event = new RegisterUserIntegrationEvent(id, date) - { - Account = "lisa", - Password = "123456" - }; - Assert.IsTrue(@event.CreationTime == date); - Assert.IsTrue(@event.Id == id); - } - - public class IntegrationEventLogService : IIntegrationEventLogService - { - public Task MarkEventAsFailedAsync(Guid eventId) - { - return Task.CompletedTask; - } - - public Task DeleteExpiresAsync(DateTime expiresAt, int batchCount = 1000, CancellationToken token = new CancellationToken()) - { - throw new NotImplementedException(); - } - - public Task MarkEventAsInProgressAsync(Guid eventId) - { - return Task.CompletedTask; - } - - public Task MarkEventAsPublishedAsync(Guid eventId) - { - return Task.CompletedTask; - } - - public Task> RetrieveEventLogsFailedToPublishAsync(int retryBatchSize = 200, int maxRetryTimes = 10, int minimumRetryInterval = 60) - { - return Task.FromResult(new List().AsEnumerable()); - } - - public Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) - { - return Task.CompletedTask; - } - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj deleted file mode 100644 index cedee4cdf..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs deleted file mode 100644 index d2b2f3b43..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs +++ /dev/null @@ -1,16 +0,0 @@ -global using Dapr.Client; -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.Events; -global using MASA.Contrib.Dispatcher.IntegrationEvents.Tests.Events; -global using MASA.Utils.Models.Config; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System.Data.Common; diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs deleted file mode 100644 index 9d29350cf..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Domain.Entities; - -public class User -{ - public string Id { get; set; } - - public string Name { get; set; } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs deleted file mode 100644 index 445a38cec..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; - -public abstract record IntegrationEvent : IIntegrationEvent -{ - public Guid Id { get; init; } - - public DateTime CreationTime { get; init; } - - [JsonIgnore] - public IUnitOfWork? UnitOfWork { get; set; } - - public abstract string Topic { get; set; } - - public IntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } - - public IntegrationEvent(Guid Id, DateTime CreationTime) - { - this.Id = Id; - this.CreationTime = CreationTime; - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs deleted file mode 100644 index 231cd8e81..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; - -internal record OrderPaymentSucceededIntegrationEvent : IntegrationEvent -{ - public string OrderId { get; set; } - - public long PaymentTime { get; set; } - - public override string Topic { get; set; } = nameof(OrderPaymentSucceededIntegrationEvent); -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs deleted file mode 100644 index 70cbbd047..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Infrastructure; - -internal class CustomDbContext : IntegrationEventLogContext -{ - public DbSet Users { get; set; } = null!; - - public CustomDbContext(MasaDbContextOptions options) : base(options) - { - - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs deleted file mode 100644 index 0d21c95eb..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; - -[TestClass] -public class IntegrationEventLogContextTest : TestBase -{ - [TestMethod] - public void TestCreateDbContext() - { - var serviceProvider = CreateDefaultProvider(); - var dbContext = serviceProvider.GetRequiredService(); - var entity = dbContext.Model.GetEntityTypes().FirstOrDefault(entityType => entityType.Name == typeof(IntegrationEventLog).FullName)!; - - Assert.IsTrue(entity.GetTableName() == "IntegrationEventLog"); - var properties = entity.GetProperties().ToList(); - Assert.IsTrue(properties.Where(x => x.Name == "Id").Select(x => x.IsPrimaryKey()).FirstOrDefault()); - Assert.IsFalse(properties.Where(x => x.Name == "Id").Select(x => x.IsNullable).FirstOrDefault()); - Assert.IsFalse(properties.Where(x => x.Name == "Content").Select(x => x.IsNullable).FirstOrDefault()); - Assert.IsFalse(properties.Where(x => x.Name == "CreationTime").Select(x => x.IsNullable).FirstOrDefault()); - Assert.IsFalse(properties.Where(x => x.Name == "State").Select(x => x.IsNullable).FirstOrDefault()); - Assert.IsFalse(properties.Where(x => x.Name == "TimesSent").Select(x => x.IsNullable).FirstOrDefault()); - Assert.IsFalse(properties.Where(x => x.Name == "EventTypeName").Select(x => x.IsNullable).FirstOrDefault()); - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs deleted file mode 100644 index 9931bd08a..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; - -[TestClass] -public class IntegrationEventLogServiceTest : TestBase -{ - [TestMethod] - public async Task TestNullDbTransactionAsync() - { - DbTransaction transaction = null!; - var @event = new OrderPaymentSucceededIntegrationEvent() - { - OrderId = "1234567890123", - PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds - }; - var serviceProvider = CreateDefaultProvider(); - var dbContext = serviceProvider.GetRequiredService(); - var eventLogService = serviceProvider.GetRequiredService(); - await Assert.ThrowsExceptionAsync(async () => await eventLogService.SaveEventAsync(@event, transaction)); - } - - [TestMethod] - public void TestMultUseEventLogService() - { - var serviceProvider = CreateDefaultProvider(options => - { - options.UseEventLog(dbContextOptionsBuilder => dbContextOptionsBuilder.UseSqlite(_connection)); - }); - Assert.IsTrue(serviceProvider.GetServices().Count() == 1); - } - - [TestMethod] - public void TestNullServices() - { - var options = new DispatcherOptions(null!); - Assert.ThrowsException(() => { options.UseEventLog(options => { options.UseSqlite(base._connection); }); }); - } - - [TestMethod] - public void TestNullDbContextOptionsBuilder() - { - var options = new DispatcherOptions(new ServiceCollection()); - Assert.ThrowsException(() => { options.UseEventLog(null!); }); - } - - [TestMethod] - public void TestUseCustomDbContextByNullServices() - { - var options = new DispatcherOptions(null!); - Assert.IsNull(options.Services); - Assert.ThrowsException(() => options.UseEventLog()); - } - - [TestMethod] - public void TestGenericEventLog() - { - var options = new DispatcherOptions(new ServiceCollection()); - Assert.ThrowsException(() => options.UseEventLog()); - } - - [TestMethod] - public async Task TestCustomDbContextAsync() - { - var options = new DispatcherOptions(new ServiceCollection()); - options.Services.AddMasaDbContext(options => - options.UseSqlite(_connection).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); - - var integrationEventBus = new Mock(); - integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => - AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) - .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); - options.Services.AddScoped(serviceProvider => integrationEventBus.Object); - - options.Services.AddScoped(); - - options.UseEventLog(); - - var serviceProvider = options.Services.BuildServiceProvider(); - var eventLogService = serviceProvider.GetRequiredService(); - - var @event = new OrderPaymentSucceededIntegrationEvent() - { - OrderId = "1234567890123", - PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds - }; - - var dbContext = serviceProvider.GetRequiredService(); - await dbContext.Database.EnsureCreatedAsync(); - // using (var transaction = await dbContext.Database.BeginTransactionAsync()) - // { - // await eventLogService.SaveEventAsync(@event, - // Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); - // - // await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); - // } - } - - [TestMethod] - public async Task TestAddMultEventLog() - { - var options = new DispatcherOptions(new ServiceCollection()); - options.Services.AddMasaDbContext(options => options.UseSqlite(_connection)); - - var integrationEventBus = new Mock(); - integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => - AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) - .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); - options.Services.AddScoped(serviceProvider => integrationEventBus.Object); - - options.Services.AddScoped(); - - options.UseEventLog().UseEventLog(); - - var serviceProvider = options.Services.BuildServiceProvider(); - var eventLogService = serviceProvider.GetRequiredService(); - - var @event = new OrderPaymentSucceededIntegrationEvent() - { - OrderId = "1234567890123", - PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds - }; - - var dbContext = serviceProvider.GetRequiredService(); - await dbContext.Database.EnsureCreatedAsync(); - // using (var transaction = dbContext.Database.BeginTransaction()) - // { - // await eventLogService.SaveEventAsync(@event, - // Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); - // - // await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); - // } - } - - [TestMethod] - public void TestGetIntegrationEventLogService() - { - var services = new ServiceCollection(); - services.AddDbContext(options => options.UseSqlite(_connection)); - var serviceProvider = services.BuildServiceProvider(); - Assert.ThrowsException(() => - { - var dbContext = serviceProvider.GetServices(); - }); - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj deleted file mode 100644 index dd5292367..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs deleted file mode 100644 index 57215634b..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; - -public class TestBase -{ - protected readonly SqliteConnection _connection; - - protected TestBase() - { - _connection = new SqliteConnection("DataSource=:memory:"); - _connection.Open(); - } - - public void Dispose() - { - _connection.Close(); - } - - protected IServiceProvider CreateDefaultProvider(Action? action = null) - { - var services = new ServiceCollection(); - services.AddScoped(); - var options = new DispatcherOptions(services); - options.UseEventLog(options => options.UseSqlite(_connection)); - action?.Invoke(options); - - var integrationEventBus = new Mock(); - integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()).Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); - services.AddScoped(serviceProvider => integrationEventBus.Object); - return services.BuildServiceProvider(); - } -} - -public class DispatcherOptions : IDispatcherOptions -{ - public IServiceCollection Services { get; init; } - - public DispatcherOptions(IServiceCollection services) - { - this.Services = services; - } -} diff --git a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs b/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs deleted file mode 100644 index df7c1433c..000000000 --- a/test/MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs +++ /dev/null @@ -1,15 +0,0 @@ -global using MASA.BuildingBlocks.Data.UoW; -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents; -global using MASA.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; -global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Domain.Entities; -global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; -global using MASA.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Infrastructure; -global using MASA.Utils.Data.EntityFrameworkCore; -global using Microsoft.Data.Sqlite; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Moq; -global using System.Data.Common; -global using System.Text.Json.Serialization; diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs deleted file mode 100644 index f4c4a22c3..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Commands/CreateProductionCommand.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Commands; - -public record CreateProductionCommand : Command -{ - public string Name { get; set; } - - public int Count { get; set; } -} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs deleted file mode 100644 index aa936c80e..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CqrsTest.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests; - -[TestClass] -public class CQRSTest -{ - private IServiceCollection _services; - private IServiceProvider _serviceProvider; - private IEventBus _eventBus; - - [TestInitialize] - public void Initialize() - { - _services = new ServiceCollection(); - _services.AddEventBus(); - _serviceProvider = _services.BuildServiceProvider(); - _eventBus = _serviceProvider.GetRequiredService(); - } - - - [DataTestMethod] - [DataRow("")] - [DataRow("tom")] - public void TestCommand(string name) - { - var command = new CreateProductionCommand() - { - Name = name, - Count = 0 - }; - _eventBus.PublishAsync(command); - if (string.IsNullOrEmpty(name)) - { - Assert.IsTrue(command.Count == 2); - } - else - { - Assert.IsTrue(command.Count == 1); - } - } - - [TestMethod] - public void TestQuery() - { - var query = new ProductionItemQuery() - { - ProductionId = "1" - }; - _eventBus.PublishAsync(query); - Assert.IsTrue(query.Result == "Apple"); - } -} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs deleted file mode 100644 index b87ada809..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/CreateProductionCommandHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests; - -public class CreateProductionCommandHandler : CommandHandler -{ - [EventHandler(1, Dispatcher.Events.Enums.FailureLevels.ThrowAndCancel, false)] - public override Task HandleAsync(CreateProductionCommand @event) - { - @event.Count++; - if (string.IsNullOrEmpty(@event.Name)) - throw new ArgumentNullException(nameof(@event)); - - if (@event.Id == default(Guid) || @event.CreationTime > DateTime.UtcNow) - throw new ArgumentNullException(nameof(@event)); - - return Task.CompletedTask; - } - - [EventHandler(1)] - public override Task CancelAsync(CreateProductionCommand @event) - { - @event.Count++; - return base.CancelAsync(@event); - } -} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj deleted file mode 100644 index 16dccc410..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/MASA.Contrib.ReadWriteSpliting.CQRS.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ -๏ปฟ - - - net6.0 - enable - false - enable - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs deleted file mode 100644 index 43deb0445..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/ProductionQueryHandler.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests; - -public class ProductionQueryHandler : QueryHandler -{ - public override Task HandleAsync(ProductionItemQuery @event) - { - if (string.IsNullOrEmpty(@event.ProductionId)) - throw new ArgumentNullException(nameof(@event)); - - if (@event.Id == default(Guid) || @event.CreationTime > DateTime.UtcNow) - throw new ArgumentNullException(nameof(@event)); - - if (@event.ProductionId == "1") - @event.Result = "Apple"; - - return Task.CompletedTask; - } -} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs deleted file mode 100644 index 2560231b3..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/Queries/ProductionItemQuery.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Queries; - -public record ProductionItemQuery : Query -{ - public override string Result { get; set; } - - public string ProductionId { get; set; } -} diff --git a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs b/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs deleted file mode 100644 index e01b69ac4..000000000 --- a/test/MASA.Contrib.ReadWriteSpliting.CQRS.Tests/_Imports.cs +++ /dev/null @@ -1,8 +0,0 @@ -global using MASA.BuildingBlocks.Dispatcher.Events; -global using MASA.Contrib.Dispatcher.Events; -global using MASA.Contrib.ReadWriteSpliting.CQRS.Commands; -global using MASA.Contrib.ReadWriteSpliting.CQRS.Queries; -global using MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Commands; -global using MASA.Contrib.ReadWriteSpliting.CQRS.Tests.Queries; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj b/test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj deleted file mode 100644 index 6d6fbd4ec..000000000 --- a/test/MASA.Contrib.Service.MinimalAPIs.Tests/MASA.Contrib.Service.MinimalAPIs.Tests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net6.0 - enable - false - enable - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs b/test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs deleted file mode 100644 index dacff249c..000000000 --- a/test/MASA.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace MASA.Contrib.Service.MinimalAPIs.Tests; - -[TestClass] -public class MinimalAPITest -{ - private WebApplicationBuilder _builder; - - [TestInitialize] - public void Initialize() - { - _builder = WebApplication.CreateBuilder(); - } - - [TestMethod] - public void TestAddMultiServices() - { - _builder.Services.AddServices(_builder); - _builder.Services.AddServices(_builder); - var servicePrvider = _builder.Services.BuildServiceProvider(); - Assert.IsTrue(servicePrvider.GetServices>().Count() == 1); - } - - [TestMethod] - public void AddService() - { - var app = _builder.AddServices(); - Assert.IsTrue(_builder.Services.Any(service => service.ServiceType == typeof(CustomService) && service.Lifetime == ServiceLifetime.Scoped)); - - var servicePrvider = _builder.Services.BuildServiceProvider(); - var customService = servicePrvider.GetService(); - Assert.IsNotNull(customService); - - Assert.ReferenceEquals(customService.App, app); - - Assert.ReferenceEquals(customService.Services, _builder.Services); - - Assert.IsNotNull(customService.GetRequiredService()); - Assert.IsNotNull(customService.GetService()); - - Assert.IsTrue(customService.Test() == 1); - - var newCustomService = servicePrvider.CreateScope().ServiceProvider.GetService(); - Assert.IsNotNull(newCustomService); - - Assert.IsTrue(newCustomService.Test() == 1); - - } -} diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs b/test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs deleted file mode 100644 index b0ce6d588..000000000 --- a/test/MASA.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MASA.Contrib.Service.MinimalAPIs.Tests.Services; - -public class CustomService : ServiceBase -{ - private int _times = 0; - - public CustomService(IServiceCollection services) : base(services) - { - _times++; - } - - public int Test() => _times; -} diff --git a/test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs b/test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs deleted file mode 100644 index 7315683bd..000000000 --- a/test/MASA.Contrib.Service.MinimalAPIs.Tests/_Imports.cs +++ /dev/null @@ -1,4 +0,0 @@ -global using MASA.Contrib.Service.MinimalAPIs.Tests.Services; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj b/test/MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj deleted file mode 100644 index f70300f46..000000000 --- a/test/MASA.Contribs.DDD.Domain.Entities.Tests/MASA.Contribs.DDD.Domain.Entities.Tests.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - false - - - - - - - diff --git a/test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs b/test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs deleted file mode 100644 index 342e042f8..000000000 --- a/test/MASA.Contribs.DDD.Domain.Entities.Tests/Users.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MASA.Contribs.DDD.Domain.Entities.Tests; - -public class Users : AggregateRoot -{ - public string Name { get; set; } -} - diff --git a/test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs b/test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs deleted file mode 100644 index d6a2a9e7d..000000000 --- a/test/MASA.Contribs.DDD.Domain.Entities.Tests/_Imports.cs +++ /dev/null @@ -1 +0,0 @@ -global using MASA.BuildingBlocks.DDD.Domain.Entities; From 441a75e69511c24657e0700b1f450dd9e5469815 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 24 Feb 2022 18:54:31 +0800 Subject: [PATCH 07/10] refactor: change MASA.Contrib to Masa.Contrib --- .editorconfig | 32 + .github/workflows/package_push_nuget.org.yml | 42 + .gitignore | 222 +++++ .gitmodules | 3 + Directory.Build.props | 24 + LICENSE.txt | 21 + Masa.Contrib.sln | 600 +++++++++++++ NuGet.Config | 6 + README.md | 144 ++++ README.zh-CN.md | 144 ++++ docs/LoadEvent.md | 10 + packageIcon.png | Bin 0 -> 9523 bytes .../ConfigurationApiClient.cs | 158 ++++ .../ConfigurationApiManage.cs | 28 + .../Internal/ConfigFormats.cs | 8 + .../Internal/ConfigurationAPIBase.cs | 37 + .../Internal/Constants.cs | 12 + .../Internal/DccConfigurationRepository.cs | 92 ++ .../Internal/DccFactory.cs | 20 + .../Internal/Model/Property.cs | 8 + .../Internal/Model/PublishRelease.cs | 8 + .../Parser/JsonConfigurationParser.cs | 101 +++ .../Parser/PropertyConfigurationParser.cs | 7 + .../Masa.Contrib.BasicAbility.Dcc.csproj | 23 + .../MasaConfigurationExtensions.cs | 204 +++++ .../Options/DccConfigurationOptions.cs | 13 + .../Options/DccExpandSectionOptions.cs | 9 + .../Options/DccSectionOptions.cs | 24 + .../Masa.Contrib.BasicAbility.Dcc/README.md | 164 ++++ .../README.zh-CN.md | 155 ++++ .../Masa.Contrib.BasicAbility.Dcc/_Imports.cs | 24 + src/BuildingBlocks/MASA.BuildingBlocks | 1 + .../LocalMasaConfigurationRepository.cs | 71 ++ .../Masa.Contrib.Configuration.csproj | 17 + .../MasaConfigurationBuilder.cs | 52 ++ .../MasaConfigurationExtensions.cs | 35 + .../MasaConfigurationOptions.cs | 19 + .../MasaConfigurationProvider.cs | 64 ++ .../MasaConfigurationSource.cs | 17 + .../MasaRelationOptions.cs | 33 + .../Masa.Contrib.Configuration/README.md | 135 +++ .../README.zh-CN.md | 140 +++ .../ServiceCollectionExtensions.cs | 138 +++ .../Masa.Contrib.Configuration/_Imports.cs | 15 + .../Masa.Contrib.Data.Contracts.EF.csproj | 19 + .../Masa.Contrib.Data.Contracts.EF/README.md | 31 + .../README.zh-CN.md | 31 + .../ServiceCollectionExtensions.cs | 23 + .../SoftDelete/QueryFilterProvider.cs | 19 + .../SoftDelete/SoftDeleteSaveChangesFilter.cs | 17 + .../_Imports.cs | 9 + .../DispatcherOptionsExtensions.cs | 43 + .../Masa.Contrib.Data.UoW.EF.csproj | 20 + src/Data/Masa.Contrib.Data.UoW.EF/README.md | 20 + .../Masa.Contrib.Data.UoW.EF/README.zh-CN.md | 20 + .../Masa.Contrib.Data.UoW.EF/Transaction.cs | 9 + .../Masa.Contrib.Data.UoW.EF/UnitOfWork.cs | 66 ++ src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs | 11 + .../DispatcherOptionsExtensions.cs | 34 + .../Internal/LinqExtensions.cs | 93 ++ .../ServiceCollectionRepositoryExtensions.cs | 105 +++ ...sa.Contrib.Ddd.Domain.Repository.EF.csproj | 18 + .../README.md | 71 ++ .../README.zh-CN.md | 71 ++ .../Repository.cs | 213 +++++ .../_Imports.cs | 15 + .../Masa.Contrib.Ddd.Domain/DomainEventBus.cs | 84 ++ .../Masa.Contrib.Ddd.Domain/DomainService.cs | 8 + .../Events/DomainCommand.cs | 15 + .../Events/DomainEvent.cs | 16 + .../Events/DomainQuery.cs | 22 + .../Events/IntegrationDomainEvent.cs | 9 + .../Internal/InvokeBuilder.cs | 21 + .../Masa.Contrib.Ddd.Domain.csproj | 18 + .../Options/DispatcherOptions.cs | 40 + src/Ddd/Masa.Contrib.Ddd.Domain/README.md | 119 +++ .../Masa.Contrib.Ddd.Domain/README.zh-CN.md | 119 +++ .../ServiceCollectionExtensions.cs | 67 ++ src/Ddd/Masa.Contrib.Ddd.Domain/_Imports.cs | 15 + .../DispatcherOptionsExtensions.cs | 26 + .../Enums/FailureLevels.cs | 10 + .../Masa.Contrib.Dispatcher.Events/Event.cs | 12 + .../EventBus.cs | 67 ++ .../EventHandlerAttribute.cs | 150 ++++ .../Dispatch/DispatchRelationNetwork.cs | 99 +++ .../Internal/Dispatch/Dispatcher.cs | 68 ++ .../Internal/Dispatch/DispatcherBase.cs | 128 +++ .../Internal/Dispatch/SagaDispatcher.cs | 118 +++ .../Internal/DispatcherExtensions.cs | 24 + .../Internal/Expressions/InvokeBuilder.cs | 56 ++ .../Middleware/TransactionMiddleware.cs | 49 ++ .../Masa.Contrib.Dispatcher.Events.csproj | 22 + .../Options/DispatchRelationOptions.cs | 21 + .../Options/DispatcherOptions.cs | 36 + .../Masa.Contrib.Dispatcher.Events/README.md | 194 +++++ .../README.zh-CN.md | 194 +++++ .../ServiceCollectionExtensions.cs | 61 ++ .../Strategies/ExecutionStrategy.cs | 45 + .../Strategies/IExecutionStrategy.cs | 7 + .../Strategies/StrategyOptions.cs | 19 + .../_Imports.cs | 18 + .../DispatcherOptionsExtensions.cs | 22 + .../IProcessingServer.cs | 6 + .../IProcessor.cs | 13 + .../IntegrationEvent.cs | 18 + .../IntegrationEventBus.cs | 100 +++ .../IntegrationEventHostedService.cs | 20 + .../Internal/IntegrationEventLogItem.cs | 31 + .../Internal/LocalQueueProcessor.cs | 60 ++ ...b.Dispatcher.IntegrationEvents.Dapr.csproj | 21 + .../Options/DispatcherOptions.cs | 158 ++++ .../DeleteLocalQueueExpiresProcessor.cs | 24 + .../DeletePublishedExpireEventProcessor.cs | 32 + .../Processor/InfiniteLoopProcessor.cs | 35 + .../Processor/ProcessorBase.cs | 19 + .../Processor/RetryByDataProcessor.cs | 76 ++ .../Processor/RetryByLocalQueueProcessor.cs | 72 ++ .../README.md | 104 +++ .../README.zh-CN.md | 105 +++ .../Servers/DefaultHostedService.cs | 20 + .../ServiceCollectionExtensions.cs | 53 ++ .../_Imports.cs | 19 + .../DispatcherOptionsExtensions.cs | 56 ++ .../IntegrationEventLogContext.cs | 53 ++ .../IntegrationEventLogService.cs | 158 ++++ .../Internal/DbContextExtensions.cs | 27 + .../Internal/QueryFilterProvider.cs | 6 + .../Internal/SaveChangesFilter.cs | 6 + ...cher.IntegrationEvents.EventLogs.EF.csproj | 20 + .../README.md | 24 + .../README.zh-CN.md | 24 + .../_Imports.cs | 18 + .../Commands/Command.cs | 15 + .../Commands/CommandHandler.cs | 12 + ...Masa.Contrib.ReadWriteSpliting.Cqrs.csproj | 19 + .../Queries/Query.cs | 15 + .../Queries/QueryHandler.cs | 8 + .../README.md | 88 ++ .../README.zh-CN.md | 88 ++ .../_Imports.cs | 6 + .../Masa.Contrib.Service.MinimalAPIs.csproj | 18 + .../README.md | 53 ++ .../README.zh-CN.md | 53 ++ .../ServiceBase.cs | 119 +++ .../ServiceCollectionExtensions.cs | 42 + .../_Imports.cs | 8 + .../DccClientTest.cs | 388 +++++++++ .../DccManageTest.cs | 162 ++++ .../DccTest.cs | 799 ++++++++++++++++++ .../Internal/Common/SerializeCommon.cs | 7 + .../Internal/Config/Property.cs | 8 + .../Internal/Config/PublishRelease.cs | 8 + .../Internal/CustomTrigger.cs | 26 + .../Internal/Enum/ConfigFormats.cs | 8 + .../Internal/Model/Brands.cs | 14 + ...Masa.Contrib.BasicAbility.Dcc.Tests.csproj | 36 + .../_Imports.cs | 14 + .../appsettings.json | 23 + .../expandSections.json | 32 + .../ErrorKafkaOptions.cs | 12 + .../KafkaOptions.cs | 15 + ...iguration.ErrorSectionAutoMap.Tests.csproj | 14 + .../_Imports.cs | 2 + ...tion.MountErrorSectionAutoMap.Tests.csproj | 14 + .../MountSectionRedisOptions.cs | 12 + .../_Imports.cs | 2 + .../Config/RabbitMqOptions.cs | 16 + .../Config/RedisOptions.cs | 10 + .../Config/SystemOptions.cs | 14 + .../ConfigurationTest.cs | 266 ++++++ .../Masa.Contrib.Configuration.Tests.csproj | 43 + .../_Imports.cs | 10 + .../appsettings.json | 9 + .../rabbitMq.json | 7 + .../redis.json | 5 + .../Domain/Entities/Courses.cs | 14 + .../Domain/Entities/Students.cs | 16 + .../Infrastructure/CustomDbContext.cs | 12 + ...asa.Contrib.Data.Contracts.EF.Tests.csproj | 28 + .../SoftDeleteTest.cs | 117 +++ .../_Imports.cs | 10 + .../CustomerDbContext.cs | 44 + .../Masa.Contrib.Data.UoW.EF.Tests.csproj | 31 + .../TestBase.cs | 17 + .../TestUnitOfWork.cs | 171 ++++ .../_Imports.cs | 11 + ...in.Repository.EF.CombinedKeys.Tests.csproj | 14 + .../Students.cs | 28 + .../_Imports.cs | 1 + .../Courses.cs | 19 + ...ository.EF.CombinedKeysNoFind.Tests.csproj | 14 + .../_Imports.cs | 1 + .../Entities/User.cs | 8 + ...epository.EF.CustomRepository.Tests.csproj | 14 + .../Repositories/IUserRepository.cs | 8 + .../_Imports.cs | 2 + .../Hobbies.cs | 16 + ...d.Domain.Repository.EF.Entity.Tests.csproj | 14 + .../_Imports.cs | 1 + .../BaseRepositoryTest.cs | 78 ++ .../Domain/Entities/Address.cs | 23 + .../Domain/Entities/OrderItem.cs | 16 + .../Domain/Entities/Orders.cs | 40 + .../Domain/Repositories/IOrderRepository.cs | 6 + .../Infrastructure/CustomDbContext.cs | 23 + .../Repositories/OrderRepository.cs | 23 + ...trib.Ddd.Domain.Repository.EF.Tests.csproj | 31 + .../RepositoryTest.cs | 273 ++++++ .../TestBase.cs | 17 + .../_Imports.cs | 23 + .../DomainEventBusTest.cs | 349 ++++++++ .../DomainIntegrationEventBusTest.cs | 49 ++ .../Events/CreateProductDomainCommand.cs | 6 + .../Events/ForgetPasswordEvent.cs | 10 + .../PaymentFailedIntegrationDomainEvent.cs | 8 + .../Events/PaymentSucceededDomainEvent.cs | 6 + .../PaymentSucceededIntegraionDomainEvent.cs | 6 + .../Events/ProductItemDomainQuery.cs | 8 + ...sterUserSucceededDomainIntegrationEvent.cs | 8 + .../PaymentSucceededDomainEventHandller.cs | 19 + .../Masa.Contrib.Ddd.Domain.Tests.csproj | 29 + .../Services/UserDomainService.cs | 16 + .../Masa.Contrib.Ddd.Domain.Tests/_Imports.cs | 16 + .../Benchmarks.cs | 61 ++ .../Extensions/EventHandlers/CouponHandler.cs | 50 ++ .../Extensions/EventHandlers/NoticeHandler.cs | 63 ++ .../Extensions/Events/ForgetPasswordEvent.cs | 8 + .../Extensions/Events/RegisterUserEvent.cs | 8 + .../Middleware/LoggingMiddleware.cs | 15 + ...atcher.Events.BenchmarkDotnet.Tests.csproj | 22 + .../Program.cs | 14 + .../_Imports.cs | 15 + .../EventHandlers/AddGoodsHandler.cs | 10 + .../Events/AddGoodsEvent.cs | 10 + ....Events.CheckMethodsParameter.Tests.csproj | 14 + .../_Imports.cs | 2 + .../EventHandlers/DeleteGoodsHandler.cs | 10 + .../Events/DeleteGoodsEvent.cs | 6 + ....CheckMethodsParameterNotNull.Tests.csproj | 14 + .../_Imports.cs | 0 .../EventHandlers/AddCatalogHandler.cs | 16 + .../Events/AddCatalogEvent.cs | 8 + ...nts.CheckMethodsParameterType.Tests.csproj | 14 + .../_Imports.cs | 1 + .../EventHandlers/AddBasketHandler.cs | 14 + .../Events/AddBasketEvent.cs | 8 + ...tcher.Events.CheckMethodsType.Tests.csproj | 14 + .../_Imports.cs | 2 + .../EventHandlers/UserEventHandler.cs | 10 + .../Events/BindPhoneNumberEvent.cs | 8 + ...cher.Events.OnlyCancelHandler.Tests.csproj | 14 + .../_Imports.cs | 1 + .../EventHandlers/EditCategoryHandler.cs | 21 + .../Events/EditCategoryEvent.cs | 8 + ...tcher.Events.OrderEqualBySaga.Tests.csproj | 14 + .../_Imports.cs | 3 + .../OrderStockConfirmedHandler.cs | 14 + .../Events/OrderStockConfirmedEvent.cs | 6 + ...ts.OrderLessThanZeroByFeature.Tests.csproj | 14 + .../_Imports.cs | 2 + .../EventHandlers/EditGoodsHandler.cs | 14 + .../Events/EditGoodsEvent.cs | 10 + ...vents.OrderLessThanZeroBySaga.Tests.csproj | 14 + .../_Imports.cs | 3 + .../AssemblyResolutionTests.cs | 122 +++ .../ChoreTest.cs | 102 +++ .../ChangePasswordEventHandler.cs | 39 + .../EventHandlers/MarketingEventHandler.cs | 24 + .../OrderPaymentSucceededEventHandler.cs | 23 + .../EventHandlers/ShipOrderEventHandler.cs | 55 ++ .../EventHandlers/ShoppingCardEventHandler.cs | 14 + .../EventHandlers/TransferEventHandler.cs | 80 ++ .../EventHandlers/UserEventHandler.cs | 27 + .../Events/AddShoppingCartEvent.cs | 8 + .../Events/AddUserEvent.cs | 15 + .../Events/ChangePasswordEvent.cs | 12 + .../Events/ComputeEvent.cs | 26 + .../Events/DeductionMoneyEvent.cs | 12 + .../Events/EditUserEvent.cs | 8 + .../Events/ForgotPasswordEvent.cs | 8 + .../Events/IncreaseMoneyEvent.cs | 12 + .../OrderPaymentFailedIntegrationEvent.cs | 22 + .../Events/OrderPaymentSucceededEvent.cs | 8 + .../Events/ShipOrderEvent.cs | 10 + .../Events/TransferEvent.cs | 10 + .../FeaturesTest.cs | 278 ++++++ ...asa.Contrib.Dispatcher.Events.Tests.csproj | 41 + .../Middleware/LoggingMiddleware.cs | 14 + .../SagaTest.cs | 131 +++ .../TestBase.cs | 44 + .../_Imports.cs | 20 + .../Events/CreateUserEvent.cs | 21 + .../Events/RegisterUserIntegrationEvent.cs | 20 + .../IntegrationEventBusTest.cs | 370 ++++++++ ...atcher.IntegrationEvents.Dapr.Tests.csproj | 27 + .../_Imports.cs | 16 + .../Domain/Entities/User.cs | 8 + .../Events/IntegrationEvent.cs | 21 + .../OrderPaymentSucceededIntegrationEvent.cs | 10 + .../Infrastructure/CustomDbContext.cs | 11 + .../IntegrationEventLogContextTest.cs | 23 + .../IntegrationEventLogServiceTest.cs | 144 ++++ ...ntegrationEvents.EventLogs.EF.Tests.csproj | 28 + .../TestBase.cs | 41 + .../_Imports.cs | 15 + .../Commands/CreateProductionCommand.cs | 8 + .../CqrsTest.cs | 51 ++ .../CreateProductionCommandHandler.cs | 24 + ...ontrib.ReadWriteSpliting.Cqrs.Tests.csproj | 27 + .../ProductionQueryHandler.cs | 18 + .../Queries/ProductionItemQuery.cs | 8 + .../_Imports.cs | 8 + ...a.Contrib.Service.MinimalAPIs.Tests.csproj | 26 + .../MinimalAPITest.cs | 48 ++ .../Services/CustomService.cs | 13 + .../_Imports.cs | 4 + ....Contribs.Ddd.Domain.Entities.Tests.csproj | 14 + .../Users.cs | 7 + .../_Imports.cs | 1 + 319 files changed, 14361 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/package_push_nuget.org.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Directory.Build.props create mode 100644 LICENSE.txt create mode 100644 Masa.Contrib.sln create mode 100644 NuGet.Config create mode 100644 README.md create mode 100644 README.zh-CN.md create mode 100644 docs/LoadEvent.md create mode 100644 packageIcon.png create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/ConfigurationApiClient.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Constants.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Masa.Contrib.BasicAbility.Dcc.csproj create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.md create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.zh-CN.md create mode 100644 src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/_Imports.cs create mode 160000 src/BuildingBlocks/MASA.BuildingBlocks create mode 100644 src/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj create mode 100644 src/Configuration/Masa.Contrib.Configuration/MasaConfigurationBuilder.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/MasaConfigurationExtensions.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/MasaConfigurationOptions.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/MasaConfigurationProvider.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/MasaConfigurationSource.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/MasaRelationOptions.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/README.md create mode 100644 src/Configuration/Masa.Contrib.Configuration/README.zh-CN.md create mode 100644 src/Configuration/Masa.Contrib.Configuration/ServiceCollectionExtensions.cs create mode 100644 src/Configuration/Masa.Contrib.Configuration/_Imports.cs create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/Masa.Contrib.Data.Contracts.EF.csproj create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/README.md create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/README.zh-CN.md create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs create mode 100644 src/Data/Masa.Contrib.Data.Contracts.EF/_Imports.cs create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/Masa.Contrib.Data.UoW.EF.csproj create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/README.md create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/Transaction.cs create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/DispatcherOptionsExtensions.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/LinqExtensions.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Masa.Contrib.Ddd.Domain.Repository.EF.csproj create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.md create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.zh-CN.md create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/_Imports.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/DomainEventBus.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/DomainService.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainCommand.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainEvent.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainQuery.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Events/IntegrationDomainEvent.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Internal/InvokeBuilder.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/Options/DispatcherOptions.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/README.md create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/README.zh-CN.md create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/ServiceCollectionExtensions.cs create mode 100644 src/Ddd/Masa.Contrib.Ddd.Domain/_Imports.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Enums/FailureLevels.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Event.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventBus.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventHandlerAttribute.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.md create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.zh-CN.md create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.Events/_Imports.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md create mode 100644 src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/Command.cs create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/CommandHandler.cs create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs.csproj create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/Query.cs create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/QueryHandler.cs create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.md create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.zh-CN.md create mode 100644 src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/_Imports.cs create mode 100644 src/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj create mode 100644 src/Service/Masa.Contrib.Service.MinimalAPIs/README.md create mode 100644 src/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md create mode 100644 src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs create mode 100644 src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs create mode 100644 src/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/Masa.Contrib.BasicAbility.Dcc.Tests.csproj create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/appsettings.json create mode 100644 test/Masa.Contrib.BasicAbility.Dcc.Tests/expandSections.json create mode 100644 test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs create mode 100644 test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs create mode 100644 test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj create mode 100644 test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj create mode 100644 test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs create mode 100644 test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs create mode 100644 test/Masa.Contrib.Configuration.Tests/Config/RedisOptions.cs create mode 100644 test/Masa.Contrib.Configuration.Tests/Config/SystemOptions.cs create mode 100644 test/Masa.Contrib.Configuration.Tests/ConfigurationTest.cs create mode 100644 test/Masa.Contrib.Configuration.Tests/Masa.Contrib.Configuration.Tests.csproj create mode 100644 test/Masa.Contrib.Configuration.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Configuration.Tests/appsettings.json create mode 100644 test/Masa.Contrib.Configuration.Tests/rabbitMq.json create mode 100644 test/Masa.Contrib.Configuration.Tests/redis.json create mode 100644 test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs create mode 100644 test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs create mode 100644 test/Masa.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs create mode 100644 test/Masa.Contrib.Data.Contracts.EF.Tests/Masa.Contrib.Data.Contracts.EF.Tests.csproj create mode 100644 test/Masa.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs create mode 100644 test/Masa.Contrib.Data.Contracts.EF.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs create mode 100644 test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj create mode 100644 test/Masa.Contrib.Data.UoW.EF.Tests/TestBase.cs create mode 100644 test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs create mode 100644 test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests.csproj create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Students.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.csproj create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Hobbies.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests.csproj create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/BaseRepositoryTest.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Address.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Tests.csproj create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/TestBase.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/DomainEventBusTest.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/DomainIntegrationEventBusTest.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/CreateProductDomainCommand.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/ForgetPasswordEvent.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededDomainEvent.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/ProductItemDomainQuery.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Masa.Contrib.Ddd.Domain.Tests.csproj create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/Services/UserDomainService.cs create mode 100644 test/Masa.Contrib.Ddd.Domain.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/ChoreTest.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Masa.Contrib.Dispatcher.Events.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/SagaTest.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/TestBase.cs create mode 100644 test/Masa.Contrib.Dispatcher.Events.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs create mode 100644 test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Commands/CreateProductionCommand.cs create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CqrsTest.cs create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CreateProductionCommandHandler.cs create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.csproj create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/ProductionQueryHandler.cs create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Queries/ProductionItemQuery.cs create mode 100644 test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Service.MinimalAPIs.Tests/Masa.Contrib.Service.MinimalAPIs.Tests.csproj create mode 100644 test/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs create mode 100644 test/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs create mode 100644 test/Masa.Contrib.Service.MinimalAPIs.Tests/_Imports.cs create mode 100644 test/Masa.Contribs.Ddd.Domain.Entities.Tests/Masa.Contribs.Ddd.Domain.Entities.Tests.csproj create mode 100644 test/Masa.Contribs.Ddd.Domain.Entities.Tests/Users.cs create mode 100644 test/Masa.Contribs.Ddd.Domain.Entities.Tests/_Imports.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2250cdba0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +๏ปฟ[*.cs] + +# CS8618: ๅœจ้€€ๅ‡บๆž„้€ ๅ‡ฝๆ•ฐๆ—ถ๏ผŒไธๅฏไธบ null ็š„ๅญ—ๆฎตๅฟ…้กปๅŒ…ๅซ้ž null ๅ€ผใ€‚่ฏท่€ƒ่™‘ๅฃฐๆ˜Žไธบๅฏไปฅไธบ nullใ€‚ +dotnet_diagnostic.CS8618.severity = none + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +trim_trailing_whitespace = true +guidelines = 140 +max_line_length = 140 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8 + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct,xml,stylecop}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 \ No newline at end of file diff --git a/.github/workflows/package_push_nuget.org.yml b/.github/workflows/package_push_nuget.org.yml new file mode 100644 index 000000000..432ddb007 --- /dev/null +++ b/.github/workflows/package_push_nuget.org.yml @@ -0,0 +1,42 @@ +name: Package Push Nuget +on: + release: + types: [ created ] + +jobs: + package-build: + name: packeg build and push + runs-on: ubuntu-latest + steps: + - name: git pull + uses: actions/checkout@v2 + + - name: run a one-line script + run: env + + - name: setting dotnet version + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' + include-prerelease: true + - name: dependencies + - run: git clone -b main https://github.com/masastack/MASA.BuildingBlocks.git ./src/BuildingBlocks/MASA.BuildingBlocks + + - name: restore + run: dotnet restore + + - name: build + run: dotnet build --no-restore /p:ContinuousIntegrationBuild=true + + - name: test + run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[*.Tests]*" + + - name: codecov + uses: codecov/codecov-action@v1 + + - name: pack + run: dotnet pack --include-symbols -p:PackageVersion=$GITHUB_REF_NAME + + - name: package push + run: dotnet nuget push "**/*.symbols.nupkg" -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json + diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..158c0d5f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,222 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +# NUNIT +*.VisualState.xml +TestResult.xml +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc +*.exe +*.bak +# Chutzpah Test files +_Chutzpah* +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.cache +*.VC.db +*.VC.VC.opendb +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap +# TFS 2012 Local Workspace +$tf/ +# Guidance Automation Toolkit +*.gpState +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user +# JustCode is a .NET coding add-in +.JustCode +# TeamCity is a build add-in +_TeamCity* +# DotCover is a Code Coverage Tool +*.dotCover +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* +# MightyMoose +*.mm.* +AutoTest.Net/ +# Web workbench (sass) +.sass-cache/ +# Installshield output folder +[Ee]xpress/ +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html +# Click-Once directory +publish/ +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets +# Microsoft Azure Build Output +csx/ +*.build.csdef +# Microsoft Azure Emulator +ecf/ +rcf/ +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ +# RIA/Silverlight projects +Generated_Code/ +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +# SQL Server files +*.mdf +*.ldf +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +# Microsoft Fakes +FakesAssemblies/ +# GhostDoc plugin setting file +*.GhostDoc.xml +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +# Visual Studio 6 build log +*.plg +# Visual Studio 6 workspace options file +*.opt +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions +# Paket dependency manager +.paket/paket.exe +paket-files/ +# FAKE - F# Make +.fake/ +# JetBrains Rider +.idea/ +*.sln.iml +# CodeRush +.cr/ +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc +package-lock.json +# ๅฟฝ็•ฅwindows็”Ÿๆˆ็š„็ผฉ็•ฅๅ›พๆ–‡ไปถ +Thunbs.db +#ๅฟฝ็•ฅnugetๅบ“ +packages/ +.vscode/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..5a6352e61 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/BuildingBlocks/MASA.BuildingBlocks"] + path = src/BuildingBlocks/MASA.BuildingBlocks + url = https://github.com/masastack/MASA.BuildingBlocks.git diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..a0865029c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,24 @@ + + + $(AssemblyName) + packageIcon.png + masastack + ยฉ masastack Corporation. All rights reserved. + packageIcon.png + https://github.com/masastack/MASA.Contrib + git + true + $(MSBuildThisFileDirectory) + LICENSE.txt + + + + True + + + + True + + + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..3f96be793 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) MASA Stack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Masa.Contrib.sln b/Masa.Contrib.sln new file mode 100644 index 000000000..e15e99cc1 --- /dev/null +++ b/Masa.Contrib.sln @@ -0,0 +1,600 @@ +๏ปฟ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31521.260 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BasicAbility", "BasicAbility", "{5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{59DA3D5F-9E39-4173-8C31-126967CC189F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dispatcher", "Dispatcher", "{FBD326D3-E59C-433E-A88E-14E179E3093D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "I18n", "I18n", "{EA2668AF-28E3-42C5-9FA5-8C9FF377180E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Isolation", "Isolation", "{022D6FF5-4B65-4213-9A97-C69E2B2F99E1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Observability", "Observability", "{75050CBC-A0F2-408A-A582-54EF37450B29}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadWriteSpliting", "ReadWriteSpliting", "{509BDB5A-5D32-478F-BF27-F0470C18C7C9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SearchEngine", "SearchEngine", "{8C39C640-0E8A-43A7-890C-9742B6B70AA4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Service", "Service", "{593A3114-D1E0-47ED-BC37-58E08886175B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cqrs", "Cqrs", "{DA885E64-C5E2-4C22-8C2A-26E68A593F29}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{9F6F9899-D5F1-444A-BE56-64F949550D22}" + ProjectSection(SolutionItems) = preProject + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{38E6C400-90C0-493E-9266-C1602E229F1B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Masa.Contrib.Dispatcher.Events", "Masa.Contrib.Dispatcher.Events", "{2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Service.MinimalAPIs", "src\Service\Masa.Contrib.Service.MinimalAPIs\Masa.Contrib.Service.MinimalAPIs.csproj", "{ED301FA5-4E70-460B-A0D4-1D79D135769F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Ddd", "Ddd", "{21180442-A6A5-4239-A2AD-33FF5BB80E72}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data", "Data", "{E33ADF54-4D35-49B7-BDA6-412587CA39FF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events", "src\Dispatcher\Masa.Contrib.Dispatcher.Events\Masa.Contrib.Dispatcher.Events.csproj", "{1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests", "test\Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests\Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj", "{D55A7D3B-9C24-4029-BC03-41B28AD11DB6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests", "test\Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests\Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj", "{89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests", "test\Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests\Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj", "{2E172027-1B85-474E-A238-21B2DBDB895F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests", "test\Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests\Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj", "{62760E2C-D3D6-4824-997F-35033E6EB92C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests", "test\Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests\Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj", "{965C85E2-D94E-43DE-BFC2-B9D157242EBB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests", "test\Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests\Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj", "{7D083C64-FF32-43C4-A82C-32C4A4EC1414}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests", "test\Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests\Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj", "{DE2E92D3-929F-40E7-B8D0-502A57170A3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests", "test\Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests\Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj", "{0B139C21-8AFD-41CD-82FE-36E64FDEDE50}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.Tests", "test\Masa.Contrib.Dispatcher.Events.Tests\Masa.Contrib.Dispatcher.Events.Tests.csproj", "{3870E8F1-B269-425D-8B03-58835FD53610}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF", "src\Dispatcher\Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF\Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj", "{519A99D2-8094-48EC-A888-C0B4E017A4C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.IntegrationEvents.Dapr", "src\Dispatcher\Masa.Contrib.Dispatcher.IntegrationEvents.Dapr\Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj", "{E946A129-34ED-4069-B44E-EC7B98751006}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests", "test\Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests\Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj", "{F3DA1941-3610-48F3-901A-E2E873979BFD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.ReadWriteSpliting.Cqrs", "src\ReadWriteSpliting\Cqrs\Masa.Contrib.ReadWriteSpliting.Cqrs\Masa.Contrib.ReadWriteSpliting.Cqrs.csproj", "{EA23C277-97E4-4FBC-A53B-37048813E14F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain", "src\Ddd\Masa.Contrib.Ddd.Domain\Masa.Contrib.Ddd.Domain.csproj", "{0FF64D9E-98D7-46B1-90FB-C0364C76D65A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Repository.EF", "src\Ddd\Masa.Contrib.Ddd.Domain.Repository.EF\Masa.Contrib.Ddd.Domain.Repository.EF.csproj", "{D375233D-8AAC-4234-BC0D-3D103C600C19}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Data.Contracts.EF", "src\Data\Masa.Contrib.Data.Contracts.EF\Masa.Contrib.Data.Contracts.EF.csproj", "{33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Tests", "test\Masa.Contrib.Ddd.Domain.Tests\Masa.Contrib.Ddd.Domain.Tests.csproj", "{EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Repository.EF.Tests", "test\Masa.Contrib.Ddd.Domain.Repository.EF.Tests\Masa.Contrib.Ddd.Domain.Repository.EF.Tests.csproj", "{E893C913-98A0-4BB3-A32B-3871BE3C5C53}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests", "test\Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests\Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj", "{761C3313-A669-465F-A384-9E118FCE4F89}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Masa.Contrib.Ddd.Domain", "Masa.Contrib.Ddd.Domain", "{13EDB361-AF88-4F89-B4AB-46622BCCBC37}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Masa.Contrib.Ddd.Domain.Repository.EF", "Masa.Contrib.Ddd.Domain.Repository.EF", "{880E8263-AECC-4793-BD28-7CD03650D124}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Data.UoW.EF", "src\Data\Masa.Contrib.Data.UoW.EF\Masa.Contrib.Data.UoW.EF.csproj", "{1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Data.UoW.EF.Tests", "test\Masa.Contrib.Data.UoW.EF.Tests\Masa.Contrib.Data.UoW.EF.Tests.csproj", "{1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.BasicAbility.Dcc", "src\BasicAbility\Masa.Contrib.BasicAbility.Dcc\Masa.Contrib.BasicAbility.Dcc.csproj", "{FDF1C618-4C68-485E-A881-4FFF04EDF6E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Configuration", "src\Configuration\Masa.Contrib.Configuration\Masa.Contrib.Configuration.csproj", "{C056C688-8FFC-42BC-B4EA-EF3808A8A12C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.ReadWriteSpliting.Cqrs.Tests", "test\Masa.Contrib.ReadWriteSpliting.Cqrs.Tests\Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.csproj", "{428CDAF3-957A-4017-82EA-70737F205546}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Configuration.Tests", "test\Masa.Contrib.Configuration.Tests\Masa.Contrib.Configuration.Tests.csproj", "{DB93B639-899D-4B2C-AF8A-47B4BC6B3776}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.BasicAbility.Dcc.Tests", "test\Masa.Contrib.BasicAbility.Dcc.Tests\Masa.Contrib.BasicAbility.Dcc.Tests.csproj", "{85BCA106-4A6F-4BEE-A748-E61A24D12DBD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Masa.Contrib.Configuration", "Masa.Contrib.Configuration", "{9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Service.MinimalAPIs.Tests", "test\Masa.Contrib.Service.MinimalAPIs.Tests\Masa.Contrib.Service.MinimalAPIs.Tests.csproj", "{A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contribs.Ddd.Domain.Entities.Tests", "test\Masa.Contribs.Ddd.Domain.Entities.Tests\Masa.Contribs.Ddd.Domain.Entities.Tests.csproj", "{B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Data.Contracts.EF.Tests", "test\Masa.Contrib.Data.Contracts.EF.Tests\Masa.Contrib.Data.Contracts.EF.Tests.csproj", "{5A163042-B03A-4063-85FF-22D4C5BB5B90}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests", "test\Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests\Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj", "{84EFF9E1-6852-458F-8D57-62E3F084EA0F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests", "test\Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests\Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj", "{427822F2-7A20-4E3A-B45C-43CE855003C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests", "test\Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests\Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj", "{99067BDF-2C6A-47F8-913D-3FF9F2A69F98}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests", "test\Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests\Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests.csproj", "{A4DE46BD-1FA4-494B-80DA-6EB370686F17}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests", "test\Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests\Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.csproj", "{7909A736-6C1E-4622-9BE7-37EF0839FA05}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests", "test\Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests\Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests.csproj", "{71E02AFA-06A0-4527-923C-6666B3D66542}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests", "test\Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests\Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj", "{1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{DC578D74-98F0-4F19-A230-CFA8DAEE0AF1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Configuration", "src\BuildingBlocks\MASA.BuildingBlocks\src\Configuration\Masa.BuildingBlocks.Configuration\Masa.BuildingBlocks.Configuration.csproj", "{374B7E56-A815-4F56-A4C2-6094B5A97EE7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Data.Contracts", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\Masa.BuildingBlocks.Data.Contracts\Masa.BuildingBlocks.Data.Contracts.csproj", "{7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Data.UoW", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\Masa.BuildingBlocks.Data.UoW\Masa.BuildingBlocks.Data.UoW.csproj", "{E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Ddd.Domain", "src\BuildingBlocks\MASA.BuildingBlocks\src\Ddd\Masa.BuildingBlocks.Ddd.Domain\Masa.BuildingBlocks.Ddd.Domain.csproj", "{2971858E-2CCA-4688-B8CA-84F130AD5AA9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Dispatcher.Events", "src\BuildingBlocks\MASA.BuildingBlocks\src\Dispatcher\Masa.BuildingBlocks.Dispatcher.Events\Masa.BuildingBlocks.Dispatcher.Events.csproj", "{2503F67B-63BB-4364-8B31-1DD3049C92B7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Dispatcher.IntegrationEvents", "src\BuildingBlocks\MASA.BuildingBlocks\src\Dispatcher\Masa.BuildingBlocks.Dispatcher.IntegrationEvents\Masa.BuildingBlocks.Dispatcher.IntegrationEvents.csproj", "{88BC8170-9123-48C0-A914-11D3CE805196}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.ReadWriteSpliting.Cqrs", "src\BuildingBlocks\MASA.BuildingBlocks\src\ReadWriteSpliting\Masa.BuildingBlocks.ReadWriteSpliting.Cqrs\Masa.BuildingBlocks.ReadWriteSpliting.Cqrs.csproj", "{316B1D0A-9CF7-4E5C-A39A-8A389B075A19}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.SearchEngine.AutoComplete", "src\BuildingBlocks\MASA.BuildingBlocks\src\SearchEngine\Masa.BuildingBlocks.SearchEngine.AutoComplete\Masa.BuildingBlocks.SearchEngine.AutoComplete.csproj", "{2F4986D6-3F56-4C05-8A1D-399594F96093}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Service.MinimalAPIs", "src\BuildingBlocks\MASA.BuildingBlocks\src\Service\Masa.BuildingBlocks.Service.MinimalAPIs\Masa.BuildingBlocks.Service.MinimalAPIs.csproj", "{E72E105D-B15F-4D69-9A13-CAA49D4889D6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|x64.ActiveCfg = Debug|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Debug|x64.Build.0 = Debug|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|Any CPU.Build.0 = Release|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|x64.ActiveCfg = Release|Any CPU + {ED301FA5-4E70-460B-A0D4-1D79D135769F}.Release|x64.Build.0 = Release|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Debug|x64.Build.0 = Debug|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|Any CPU.Build.0 = Release|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|x64.ActiveCfg = Release|Any CPU + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C}.Release|x64.Build.0 = Release|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|x64.ActiveCfg = Debug|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Debug|x64.Build.0 = Debug|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|Any CPU.Build.0 = Release|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|x64.ActiveCfg = Release|Any CPU + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6}.Release|x64.Build.0 = Release|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|x64.ActiveCfg = Debug|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Debug|x64.Build.0 = Debug|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|Any CPU.Build.0 = Release|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|x64.ActiveCfg = Release|Any CPU + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B}.Release|x64.Build.0 = Release|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Debug|x64.Build.0 = Debug|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|Any CPU.Build.0 = Release|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|x64.ActiveCfg = Release|Any CPU + {2E172027-1B85-474E-A238-21B2DBDB895F}.Release|x64.Build.0 = Release|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|x64.ActiveCfg = Debug|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Debug|x64.Build.0 = Debug|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|Any CPU.Build.0 = Release|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|x64.ActiveCfg = Release|Any CPU + {62760E2C-D3D6-4824-997F-35033E6EB92C}.Release|x64.Build.0 = Release|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|x64.ActiveCfg = Debug|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Debug|x64.Build.0 = Debug|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|Any CPU.Build.0 = Release|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|x64.ActiveCfg = Release|Any CPU + {965C85E2-D94E-43DE-BFC2-B9D157242EBB}.Release|x64.Build.0 = Release|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|x64.ActiveCfg = Debug|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Debug|x64.Build.0 = Debug|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|Any CPU.Build.0 = Release|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|x64.ActiveCfg = Release|Any CPU + {7D083C64-FF32-43C4-A82C-32C4A4EC1414}.Release|x64.Build.0 = Release|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Debug|x64.Build.0 = Debug|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|Any CPU.Build.0 = Release|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|x64.ActiveCfg = Release|Any CPU + {DE2E92D3-929F-40E7-B8D0-502A57170A3E}.Release|x64.Build.0 = Release|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Debug|x64.Build.0 = Debug|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|Any CPU.Build.0 = Release|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|x64.ActiveCfg = Release|Any CPU + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50}.Release|x64.Build.0 = Release|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|x64.ActiveCfg = Debug|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Debug|x64.Build.0 = Debug|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Release|Any CPU.Build.0 = Release|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Release|x64.ActiveCfg = Release|Any CPU + {3870E8F1-B269-425D-8B03-58835FD53610}.Release|x64.Build.0 = Release|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Debug|x64.Build.0 = Debug|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|Any CPU.Build.0 = Release|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|x64.ActiveCfg = Release|Any CPU + {519A99D2-8094-48EC-A888-C0B4E017A4C1}.Release|x64.Build.0 = Release|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|x64.ActiveCfg = Debug|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Debug|x64.Build.0 = Debug|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Release|Any CPU.Build.0 = Release|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Release|x64.ActiveCfg = Release|Any CPU + {E946A129-34ED-4069-B44E-EC7B98751006}.Release|x64.Build.0 = Release|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|x64.ActiveCfg = Debug|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Debug|x64.Build.0 = Debug|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|Any CPU.Build.0 = Release|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|x64.ActiveCfg = Release|Any CPU + {F3DA1941-3610-48F3-901A-E2E873979BFD}.Release|x64.Build.0 = Release|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Debug|x64.Build.0 = Debug|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|Any CPU.Build.0 = Release|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|x64.ActiveCfg = Release|Any CPU + {EA23C277-97E4-4FBC-A53B-37048813E14F}.Release|x64.Build.0 = Release|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|x64.ActiveCfg = Debug|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Debug|x64.Build.0 = Debug|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|Any CPU.Build.0 = Release|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|x64.ActiveCfg = Release|Any CPU + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A}.Release|x64.Build.0 = Release|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|x64.ActiveCfg = Debug|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Debug|x64.Build.0 = Debug|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|Any CPU.Build.0 = Release|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|x64.ActiveCfg = Release|Any CPU + {D375233D-8AAC-4234-BC0D-3D103C600C19}.Release|x64.Build.0 = Release|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|x64.ActiveCfg = Debug|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Debug|x64.Build.0 = Debug|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|Any CPU.Build.0 = Release|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|x64.ActiveCfg = Release|Any CPU + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012}.Release|x64.Build.0 = Release|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|x64.ActiveCfg = Debug|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Debug|x64.Build.0 = Debug|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|Any CPU.Build.0 = Release|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|x64.ActiveCfg = Release|Any CPU + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1}.Release|x64.Build.0 = Release|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|x64.ActiveCfg = Debug|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Debug|x64.Build.0 = Debug|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|Any CPU.Build.0 = Release|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|x64.ActiveCfg = Release|Any CPU + {E893C913-98A0-4BB3-A32B-3871BE3C5C53}.Release|x64.Build.0 = Release|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|x64.ActiveCfg = Debug|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Debug|x64.Build.0 = Debug|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Release|Any CPU.Build.0 = Release|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Release|x64.ActiveCfg = Release|Any CPU + {761C3313-A669-465F-A384-9E118FCE4F89}.Release|x64.Build.0 = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Debug|x64.Build.0 = Debug|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|Any CPU.Build.0 = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|x64.ActiveCfg = Release|Any CPU + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F}.Release|x64.Build.0 = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|x64.ActiveCfg = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Debug|x64.Build.0 = Debug|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|Any CPU.Build.0 = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|x64.ActiveCfg = Release|Any CPU + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7}.Release|x64.Build.0 = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|x64.ActiveCfg = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Debug|x64.Build.0 = Debug|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|Any CPU.Build.0 = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|x64.ActiveCfg = Release|Any CPU + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8}.Release|x64.Build.0 = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Debug|x64.Build.0 = Debug|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|Any CPU.Build.0 = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|x64.ActiveCfg = Release|Any CPU + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C}.Release|x64.Build.0 = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|Any CPU.Build.0 = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|x64.ActiveCfg = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Debug|x64.Build.0 = Debug|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|Any CPU.ActiveCfg = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|Any CPU.Build.0 = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|x64.ActiveCfg = Release|Any CPU + {428CDAF3-957A-4017-82EA-70737F205546}.Release|x64.Build.0 = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Debug|x64.Build.0 = Debug|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|Any CPU.Build.0 = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|x64.ActiveCfg = Release|Any CPU + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776}.Release|x64.Build.0 = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|x64.ActiveCfg = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Debug|x64.Build.0 = Debug|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|Any CPU.Build.0 = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|x64.ActiveCfg = Release|Any CPU + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD}.Release|x64.Build.0 = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Debug|x64.Build.0 = Debug|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|Any CPU.Build.0 = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|x64.ActiveCfg = Release|Any CPU + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD}.Release|x64.Build.0 = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|x64.ActiveCfg = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Debug|x64.Build.0 = Debug|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|Any CPU.Build.0 = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|x64.ActiveCfg = Release|Any CPU + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826}.Release|x64.Build.0 = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|x64.ActiveCfg = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Debug|x64.Build.0 = Debug|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|Any CPU.Build.0 = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|x64.ActiveCfg = Release|Any CPU + {5A163042-B03A-4063-85FF-22D4C5BB5B90}.Release|x64.Build.0 = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|x64.ActiveCfg = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Debug|x64.Build.0 = Debug|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|Any CPU.Build.0 = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|x64.ActiveCfg = Release|Any CPU + {84EFF9E1-6852-458F-8D57-62E3F084EA0F}.Release|x64.Build.0 = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Debug|x64.Build.0 = Debug|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|Any CPU.Build.0 = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|x64.ActiveCfg = Release|Any CPU + {427822F2-7A20-4E3A-B45C-43CE855003C1}.Release|x64.Build.0 = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|x64.ActiveCfg = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Debug|x64.Build.0 = Debug|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|Any CPU.Build.0 = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|x64.ActiveCfg = Release|Any CPU + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98}.Release|x64.Build.0 = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Debug|x64.Build.0 = Debug|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|Any CPU.Build.0 = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|x64.ActiveCfg = Release|Any CPU + {A4DE46BD-1FA4-494B-80DA-6EB370686F17}.Release|x64.Build.0 = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|x64.ActiveCfg = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Debug|x64.Build.0 = Debug|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|Any CPU.Build.0 = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|x64.ActiveCfg = Release|Any CPU + {7909A736-6C1E-4622-9BE7-37EF0839FA05}.Release|x64.Build.0 = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|x64.ActiveCfg = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Debug|x64.Build.0 = Debug|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|Any CPU.Build.0 = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|x64.ActiveCfg = Release|Any CPU + {71E02AFA-06A0-4527-923C-6666B3D66542}.Release|x64.Build.0 = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Debug|x64.Build.0 = Debug|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|Any CPU.Build.0 = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|x64.ActiveCfg = Release|Any CPU + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C}.Release|x64.Build.0 = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|x64.ActiveCfg = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Debug|x64.Build.0 = Debug|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|Any CPU.Build.0 = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|x64.ActiveCfg = Release|Any CPU + {374B7E56-A815-4F56-A4C2-6094B5A97EE7}.Release|x64.Build.0 = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Debug|x64.Build.0 = Debug|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|Any CPU.Build.0 = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|x64.ActiveCfg = Release|Any CPU + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B}.Release|x64.Build.0 = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Debug|x64.Build.0 = Debug|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|Any CPU.Build.0 = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|x64.ActiveCfg = Release|Any CPU + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A}.Release|x64.Build.0 = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|x64.ActiveCfg = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Debug|x64.Build.0 = Debug|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|Any CPU.Build.0 = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|x64.ActiveCfg = Release|Any CPU + {2971858E-2CCA-4688-B8CA-84F130AD5AA9}.Release|x64.Build.0 = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Debug|x64.Build.0 = Debug|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|Any CPU.Build.0 = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|x64.ActiveCfg = Release|Any CPU + {2503F67B-63BB-4364-8B31-1DD3049C92B7}.Release|x64.Build.0 = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|x64.ActiveCfg = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Debug|x64.Build.0 = Debug|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|Any CPU.Build.0 = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|x64.ActiveCfg = Release|Any CPU + {88BC8170-9123-48C0-A914-11D3CE805196}.Release|x64.Build.0 = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|x64.ActiveCfg = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Debug|x64.Build.0 = Debug|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|Any CPU.Build.0 = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|x64.ActiveCfg = Release|Any CPU + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19}.Release|x64.Build.0 = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Debug|x64.Build.0 = Debug|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|Any CPU.Build.0 = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|x64.ActiveCfg = Release|Any CPU + {2F4986D6-3F56-4C05-8A1D-399594F96093}.Release|x64.Build.0 = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Debug|x64.Build.0 = Debug|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|Any CPU.Build.0 = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|x64.ActiveCfg = Release|Any CPU + {E72E105D-B15F-4D69-9A13-CAA49D4889D6}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {59DA3D5F-9E39-4173-8C31-126967CC189F} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {FBD326D3-E59C-433E-A88E-14E179E3093D} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {EA2668AF-28E3-42C5-9FA5-8C9FF377180E} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {75050CBC-A0F2-408A-A582-54EF37450B29} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {509BDB5A-5D32-478F-BF27-F0470C18C7C9} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {8C39C640-0E8A-43A7-890C-9742B6B70AA4} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {593A3114-D1E0-47ED-BC37-58E08886175B} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {DA885E64-C5E2-4C22-8C2A-26E68A593F29} = {509BDB5A-5D32-478F-BF27-F0470C18C7C9} + {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {ED301FA5-4E70-460B-A0D4-1D79D135769F} = {593A3114-D1E0-47ED-BC37-58E08886175B} + {21180442-A6A5-4239-A2AD-33FF5BB80E72} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {E33ADF54-4D35-49B7-BDA6-412587CA39FF} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {1B44F2E7-28F5-4E88-B8A8-3F336800FD5C} = {FBD326D3-E59C-433E-A88E-14E179E3093D} + {D55A7D3B-9C24-4029-BC03-41B28AD11DB6} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {89B2A693-CD8A-4EA2-9991-2CEE44C4D04B} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {2E172027-1B85-474E-A238-21B2DBDB895F} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {62760E2C-D3D6-4824-997F-35033E6EB92C} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {965C85E2-D94E-43DE-BFC2-B9D157242EBB} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {7D083C64-FF32-43C4-A82C-32C4A4EC1414} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {DE2E92D3-929F-40E7-B8D0-502A57170A3E} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {0B139C21-8AFD-41CD-82FE-36E64FDEDE50} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {3870E8F1-B269-425D-8B03-58835FD53610} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {519A99D2-8094-48EC-A888-C0B4E017A4C1} = {FBD326D3-E59C-433E-A88E-14E179E3093D} + {E946A129-34ED-4069-B44E-EC7B98751006} = {FBD326D3-E59C-433E-A88E-14E179E3093D} + {F3DA1941-3610-48F3-901A-E2E873979BFD} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {EA23C277-97E4-4FBC-A53B-37048813E14F} = {DA885E64-C5E2-4C22-8C2A-26E68A593F29} + {0FF64D9E-98D7-46B1-90FB-C0364C76D65A} = {21180442-A6A5-4239-A2AD-33FF5BB80E72} + {D375233D-8AAC-4234-BC0D-3D103C600C19} = {21180442-A6A5-4239-A2AD-33FF5BB80E72} + {33F3BA7C-5BCB-4FB5-8E7E-2F7E29D92012} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF} + {EAC0AA32-AE9B-4908-AF05-927B70A2B8A1} = {13EDB361-AF88-4F89-B4AB-46622BCCBC37} + {E893C913-98A0-4BB3-A32B-3871BE3C5C53} = {880E8263-AECC-4793-BD28-7CD03650D124} + {761C3313-A669-465F-A384-9E118FCE4F89} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {13EDB361-AF88-4F89-B4AB-46622BCCBC37} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {880E8263-AECC-4793-BD28-7CD03650D124} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {1265AE3C-B5FD-4339-8A7D-BC598E6E1C9F} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF} + {1B16DD58-0847-45A7-AF93-53EBFBEDAAE7} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {FDF1C618-4C68-485E-A881-4FFF04EDF6E8} = {5DFAF4A2-ECB5-46E4-904D-1EA5F48B2D48} + {C056C688-8FFC-42BC-B4EA-EF3808A8A12C} = {59DA3D5F-9E39-4173-8C31-126967CC189F} + {428CDAF3-957A-4017-82EA-70737F205546} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {DB93B639-899D-4B2C-AF8A-47B4BC6B3776} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} + {85BCA106-4A6F-4BEE-A748-E61A24D12DBD} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {A5C1EF6B-A3B5-4D0C-8373-F854EE7EF4AD} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {B29ABF5D-AFA8-4480-B74E-3ACB6FAAA826} = {13EDB361-AF88-4F89-B4AB-46622BCCBC37} + {5A163042-B03A-4063-85FF-22D4C5BB5B90} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {84EFF9E1-6852-458F-8D57-62E3F084EA0F} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} + {427822F2-7A20-4E3A-B45C-43CE855003C1} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669} + {99067BDF-2C6A-47F8-913D-3FF9F2A69F98} = {880E8263-AECC-4793-BD28-7CD03650D124} + {A4DE46BD-1FA4-494B-80DA-6EB370686F17} = {880E8263-AECC-4793-BD28-7CD03650D124} + {7909A736-6C1E-4622-9BE7-37EF0839FA05} = {880E8263-AECC-4793-BD28-7CD03650D124} + {71E02AFA-06A0-4527-923C-6666B3D66542} = {880E8263-AECC-4793-BD28-7CD03650D124} + {1A86AE9B-A57D-43D2-9E8C-5ED0C1E6041C} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} + {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9} + {374B7E56-A815-4F56-A4C2-6094B5A97EE7} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {7DAC3708-4415-490B-AAE8-0DAA74E2EA8B} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {E5A8DB4E-1205-4CE9-B802-3D6ADADF737A} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {2971858E-2CCA-4688-B8CA-84F130AD5AA9} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {2503F67B-63BB-4364-8B31-1DD3049C92B7} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {88BC8170-9123-48C0-A914-11D3CE805196} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {316B1D0A-9CF7-4E5C-A39A-8A389B075A19} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {2F4986D6-3F56-4C05-8A1D-399594F96093} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {E72E105D-B15F-4D69-9A13-CAA49D4889D6} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} + EndGlobalSection +EndGlobal diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..3f0e00340 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,6 @@ +๏ปฟ + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..012f41f47 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +[ไธญ](README.zh-CN.md) | EN + +[![codecov](https://codecov.io/gh/masastack/Masa.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/Masa.Contrib) + +# Masa.Contrib + +The purpose of Masa.Contrib is based on [Masa.BuildingBlocks](https://github.com/masastack/Masa.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [Masa Stack](https://github.com/masastack) and [Masa Labs](https://github.com/masalabs) projects. + +## Structure + +```c# +Masa.Contrib +โ”œโ”€โ”€ solution items +โ”‚ โ”œโ”€โ”€ nuget.config +โ”œโ”€โ”€ src +โ”‚ โ”œโ”€โ”€ BasicAbility +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.BasicAbility.Dcc ConfigurationAPI +โ”‚ โ”œโ”€โ”€ Configuration +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Configuration +โ”‚ โ”œโ”€โ”€ Data +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF Unit of work +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Data.Contracts.EF Protocol EF version +โ”‚ โ”œโ”€โ”€ DDD +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain In-process and cross-process support +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF +โ”‚ โ”œโ”€โ”€ Dispatcher +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events In-process event +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.Dapr +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF Cross-process event +โ”‚ โ”œโ”€โ”€ ReadWriteSpliting +โ”‚ โ”‚ โ””โ”€โ”€ CQRS +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.ReadWriteSpliting.CQRS CQRS +โ”‚ โ”œโ”€โ”€ Service +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Service.MinimalAPIs Best practices for [MinimalAPI] +โ”œโ”€โ”€ test +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.BenchmarkDotnetTest +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF.Tests +``` + +## Feature + +### 1. MinimalAPI + +What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[Usage introduction](/src/Service/Masa.Contrib.Service.MinimalAPIs/README.md) + +> Advantage๏ผš +> +> 1. Classify APIs and add them to different Services to make the Service structure clearer and get rid of running account programming + +### 2. EventBus + +[Usage introduction](/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.md) + +> Advantage๏ผš +> +> 1. Arrangement of Handler +> 2. Implement [Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) +> 3. Middleware +> 4. Transaction + +> Effect๏ผš +> +> 1. Event and Handler decoupling +> 2. Arrangement of Handler +> 3. Implement [Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) +> 4. Middleware +> 5. Transaction + +### 3. CQRS + +what is[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[Usage introduction](/src/ReadWriteSpliting/CQRS/Masa.Contrib.ReadWriteSpliting.CQRS/README.md) + +### 4. IntegrationEventBus + +Realize cross-process events based on Daprใ€‚[Usage introduction](/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md) + +> Advantage๏ผšUse the same transaction to commit the user-defined context and the log to ensure atomicity and consistency + +### 5. DomainEventBus + +[Usage introduction](/src/DDD/Masa.Contrib.DDD.Domain/README.md) + +> Advantage๏ผš +> +> 1. CQRS +> 2. Domain Service +> 3. Support domain events (in-process), integrated domain events (cross-process) +> 4. Support the unified sending of field events after being pushed onto the stack + +### 6. DDD + +[DDD](https://www.likecs.com/default/index/show?id=93970) // todo + + +### 7. Contracts.EF + +Protocol based on EF implementation๏ผŒ[Usage introduction](/Data/Masa.Contrib.Data.Contracts.EF/README.md) + +> Advantage๏ผš +> +> 1. Filter deleted information when querying +> 2. Soft delete + +```C# +Install-Package Masa.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + dbOptions.UseSoftDelete(builder.Services); + }); +}); + +``` + +> When the entity inherits ISoftware and is deleted, change the delete state to the modified state, and cooperate with the custom Remove operation to achieve soft deletion +> Do not query the data marked as soft deleted when querying +> When combined with EventBus, the transaction is opened after the first CUD, and the transaction rollback is supported when the entire Handler is abnormal. + +### 8. Masa.Contrib.Configuration + +Redefine Configuration, support the management of Local and ConfigurationAPI nodes, combine IOptions and IOptionsMonitor to complete configuration acquisition and configuration update subscription [Local Usage introduction](src/Configuration/Masa.Contrib.Configuration/README.md) ใ€[Dcc Usage introduction](src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.md) + +## Unit testing rules + +To ensure the reliability of the entire source code, the unit test coverage is at least 90% + +## โ˜€๏ธ License agreement + +[![Masa.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 000000000..3d962f1c8 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,144 @@ +๏ปฟไธญ | [EN](README.md) + +[![codecov](https://codecov.io/gh/masastack/Masa.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/Masa.Contrib) + +# Masa.Contrib + +Masa.Contribๆ˜ฏๅŸบไบŽ[Masa.BuildingBlocks](https://github.com/masastack/Masa.BuildingBlocks)ๆไพ›ๅผ€ๆ”พ, ็คพๅŒบ้ฉฑๅŠจ็š„ๅฏ้‡็”จ็ป„ไปถ๏ผŒ็”จไบŽๆž„ๅปบ็ฝ‘ๆ ผๅบ”็”จ็จ‹ๅบใ€‚่ฟ™ไบ›็ป„ไปถๅฐ†่ขซ[Masa Stack](https://github.com/masastack)ๅ’Œ[Masa Labs](https://github.com/masalabs)็ญ‰้กน็›ฎไฝฟ็”จใ€‚ + +## ็ป“ๆž„ + +```c# +Masa.Contrib +โ”œโ”€โ”€ solution items +โ”‚ โ”œโ”€โ”€ nuget.config +โ”œโ”€โ”€ src +โ”‚ โ”œโ”€โ”€ BasicAbility +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.BasicAbility.Dcc ConfigurationAPI +โ”‚ โ”œโ”€โ”€ Configuration +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Configuration +โ”‚ โ”œโ”€โ”€ Data +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF ๅทฅไฝœๅ•ๅ…ƒ +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Data.Contracts.EF ่ง„็บฆEF็‰ˆ +โ”‚ โ”œโ”€โ”€ DDD +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain ่ฟ›็จ‹ๅ†…ใ€่ทจ่ฟ›็จ‹้ƒฝๆ”ฏๆŒ +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF +โ”‚ โ”œโ”€โ”€ Dispatcher +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events ่ฟ›็จ‹ๅ†…ไบ‹ไปถ +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.Dapr +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF ่ทจ่ฟ›็จ‹ไบ‹ไปถ +โ”‚ โ”œโ”€โ”€ ReadWriteSpliting +โ”‚ โ”‚ โ””โ”€โ”€ CQRS +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.ReadWriteSpliting.CQRS CQRS +โ”‚ โ”œโ”€โ”€ Service +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Service.MinimalAPIs MinimalAPIๆœ€ไฝณๅฎž่ทต +โ”œโ”€โ”€ test +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.BenchmarkDotnetTest +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF.Tests +``` + +## ็‰นๆ€ง + +### 1. MinimalAPI + +ไป€ไนˆๆ˜ฏ[MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#introducing-minimal-apis)๏ผŸ[็”จๆณ•ไป‹็ป](/src/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md) + +> ไผ˜ๅŠฟ๏ผš +> +> 1. ๅฏนAPI่ฟ›่กŒๅˆ†็ฑปๆทปๅŠ ๅˆฐไธๅŒ็š„Service๏ผŒไฝฟๅพ—Service็ป“ๆž„ๆ›ดๆธ…ๆ™ฐ๏ผŒๆ‘†่„ฑๆตๆฐด่ดฆๅผ็ผ–็จ‹ + +### 2. EventBus + +[็”จๆณ•ไป‹็ป](/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.zh-CN.md) + +> ไผ˜ๅŠฟ๏ผš +> +> 1. ๅฏนHandler็š„็ผ–ๆŽ’ +> 2. ๅฎž็Žฐ[Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) +> 3. Middleware +> 4. Transaction + +> ไฝœ็”จ๏ผš +> +> 1. EventไธŽHandler่งฃ่€ฆ +> 2. ๅฏนHandler็š„็ผ–ๆŽ’ +> 3. ๅฎž็Žฐ[Saga](https://docs.microsoft.com/zh-cn/azure/architecture/reference-architectures/saga/saga) +> 4. Middleware +> 5. Transaction + +### 3. CQRS + +ไป€ไนˆๆ˜ฏ[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[็”จๆณ•ไป‹็ป](/src/ReadWriteSpliting/CQRS/Masa.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md) + +### 4. IntegrationEventBus + +ๅŸบไบŽDaprๅฎž็Žฐ่ทจ่ฟ›็จ‹็š„ไบ‹ไปถใ€‚[็”จๆณ•ไป‹็ป](/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md) + +> ไผ˜ๅŠฟ๏ผšๅฐ†็”จๆˆท่‡ชๅฎšไน‰ไธŠไธ‹ๆ–‡ไธŽๆ—ฅๅฟ—ไฝฟ็”จๅŒไธ€ไบ‹ๅŠกๆไบค๏ผŒ็กฎไฟๅŽŸๅญๆ€งใ€ไธ€่‡ดๆ€ง + +### 5. DomainEventBus + +[็”จๆณ•ไป‹็ป](/src/DDD/Masa.Contrib.DDD.Domain/README.zh-CN.md) + +> ไผ˜ๅŠฟ๏ผš +> +> 1. CQRS +> 2. ้ข†ๅŸŸๆœๅŠก +> 3. ๆ”ฏๆŒ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ใ€้›†ๆˆ้ข†ๅŸŸไบ‹ไปถ๏ผˆ่ทจ่ฟ›็จ‹๏ผ‰ +> 4. ๆ”ฏๆŒๅฏน้ข†ๅŸŸไบ‹ไปถๅ…ˆๅŽ‹ๆ ˆๅŽ็ปŸไธ€ๅ‘้€ + +### 6. DDD + +[DDD](https://www.likecs.com/default/index/show?id=93970) // todo + + +### 7. Contracts.EF + +ๅŸบไบŽEFๅฎž็Žฐ็š„่ง„็บฆ๏ผŒ[็”จๆณ•ไป‹็ป](src/Data/Masa.Contrib.Data.Contracts.EF/README.zh-CN.md) + +> ไผ˜ๅŠฟ๏ผš +> +> 1. ๆŸฅ่ฏข็š„ๆ—ถๅ€™่ฟ‡ๆปคๅทฒๅˆ ้™ค็š„ไฟกๆฏ +> 2. ่ฝฏๅˆ ้™ค + +```C# +Install-Package Masa.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + dbOptions.UseSoftDelete(builder.Services);//ๅฏๅŠจ่ฝฏๅˆ ้™ค + }); +}); +``` + +> ๅฝ“ๅฎžไฝ“็ปงๆ‰ฟISoftware๏ผŒไธ”่ขซๅˆ ้™คๆ—ถ๏ผŒๅฐ†ๅˆ ้™ค็Šถๆ€ๆ”นไธบไฟฎๆ”น็Šถๆ€๏ผŒๅนถ้…ๅˆ่‡ชๅฎšไน‰Removeๆ“ไฝœ๏ผŒๅฎž็Žฐ่ฝฏๅˆ ้™ค +> ๆ”ฏๆŒๆŸฅ่ฏข็š„ๆ—ถๅ€™ไธๆŸฅ่ฏข่ขซๆ ‡่ฎฐ่ฝฏๅˆ ้™ค็š„ๆ•ฐๆฎ +> ไธŽEventBus็ป“ๅˆไฝฟ็”จๆ—ถ๏ผŒๅšๅˆฐไบ†็ฌฌไธ€ๆฌกCUDๅŽๅผ€ๅฏไบ‹ๅŠก๏ผŒๅฝ“ๆ•ดไธชHandlerๅ‡บ็Žฐๅผ‚ๅธธๅŽๆ”ฏๆŒไบ‹ๅŠกๅ›žๆปš + +### 8. Masa.Contrib.Configuration + +้‡ๅฎšไน‰Configuration๏ผŒๆ”ฏๆŒLocalใ€ConfigurationAPI่Š‚็‚น็š„็ฎก็†๏ผŒ็ป“ๅˆIOptionsใ€IOptionsMonitorๅฎŒๆˆ้…็ฝฎ็š„่Žทๅ–ไปฅๅŠ้…็ฝฎ็š„ๆ›ดๆ–ฐ่ฎข้˜… [Local็”จๆณ•ไป‹็ป](src/Configuration/Masa.Contrib.Configuration/README.zh-CN.md) ใ€[Dcc็”จๆณ•ไป‹็ป](src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.zh-CN.md) + +## ๅ•ๅ…ƒๆต‹่ฏ•่ง„ๅˆ™ + +ไธบ็กฎไฟๆ•ดไธชๆบ็ ็š„ๅฏ้ ๆ€ง๏ผŒๅ•ๅ…ƒๆต‹่ฏ•่ฆ†็›–็Ž‡ๆœ€ไฝŽไธบ90% + +## โ˜€๏ธ ๆŽˆๆƒๅ่ฎฎ + +[![Masa.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) + diff --git a/docs/LoadEvent.md b/docs/LoadEvent.md new file mode 100644 index 000000000..65d1db9e3 --- /dev/null +++ b/docs/LoadEvent.md @@ -0,0 +1,10 @@ +# LoadEvent + +## Getting "Event" relationship chain failed + +When Event, EventHandler, and the main project are not in the same assembly, there will be a failure to obtain the "Event" relationship chain when publishing Events through EventBus. When we use AddEventBus without a special specified assembly, the assembly under the current domain is used by default. Due to the delayed loading feature of dotnet, the acquisition of the event relationship chain is incomplete. There are the following two solutions : + +1. When using AddEventBus, specify the complete set of application assemblies used by the current project by specifying Assemblies + +2. Before using AddEventBus, by calling Event directly +Any method or class of the assembly where the EventHandler is located, make sure that the application assembly where it is located has been loaded into the current assembly ( AppDomain.CurrentDomain.GetAssemblies() ) diff --git a/packageIcon.png b/packageIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..2128805c1995656deba83316df54cb048b9264ee GIT binary patch literal 9523 zcmb_?RZtvV)GbMH4=#bh6C4s;1_`dg-8DgjI|&-xAvl9OgA6V~Cpf_dcXt_dkjq#1 z`99qG|A+3bK7046>Z-NRS!`}+MOv^g^lRHswFY3f}@9~klao-i!G2gNH zySuv|;>rBYZCF1_vuMm{8D3#v(PGHyqRWf-NXoq?Adw`92jeIH2nmfxW?&0p_#dT+ zx3(b>BY*zv^D#9$mfVeOJXjK=^U3gPq1}+S$|x44N}p@!BN?7}B8`SKi&IYN5ngBF zs);oV2_(1OIm^#Edrfz-YXcdX-|Vw*Z4GXT-R$P@ziO3WMj(?$PW=2=WRtBPGE|_} ze4Z&^GxPDbL5_#XJFr3vL59^DT;k%E`r;a;t~BRpvXFi85a&LHHhQuG?jeYqXg(N% zX1Vj}zu1OO4347=%wZ&6bN{&Who+=izhS@5s5S#Mg~<;aeG6!4x_4XdYEG1t5@`~n zZtJ3Gp#$1jaQ}pik~VM>C0|64s*TfClT0Di?6!8Bb@B?>GObvJ9`O`xwKi>R6`*pr$jH3Mt+~0m+Z`~xxr$xz zs`F8tHnbc`yd-Qp_XD<&aG*JP^I4oErdGPla&?v_IGPAnO(SBg`!=(b(uUz(OjO6` z2LV}Q_)XmDm;FJ4h0#%lYT3owLuRi=@MKc$vY6r%T=lr5S|&myNjpZgowjAU52|^f zAunQ%vNqqNe^X>=KufzZ2FYI^rYzaEdiT#c5e+8lJ~tg4dbLLqu&s%rEWg~Qv?8Lj zCsH?c-q43xmzkna{NA^4>cZ4a`lMnaVoWUXgBx{39Lw~{V+HkogNUx~Zk)C)%4&-g z&^PGSpZZ%&NM^cZ`6W2AL6^g;9K3O|PCiI%;6?3dMC0=5dvZ&iMVl1sm%qj>n~|NDVW5TB@rL>gdHdndI!Q(w~Xq z(7D_3{PKC062HjdgZh9OiVRI38Gmc#$fNI^a4C9it3tS6C`rnI^ZOO2KcCq8xWM6x-y1QQF1 zqQTh{gC(Rb4HP|0yd3l%t3vRvhye2vGqz8&d-rna2V!bAHU(5r$C43xL`2I@PVH0O zW_Qo)A50E3;Mg`ST5=)wrE4Y6?emv0@QS$4fs)U6F6^ua#a~OJL~*34fjE2IbIYOe zKMrkZX5I(~Tfe{r=}Qf>+tL1sWHRebwU0BsZGK_{or5uIP)HQCd+>P7*z&|MDC+%^ z(XWph@*yZl>?W^MNV6Ud_VRr{$ho$E^y~$Qb5)t?8|~|lc-C`^4k<;-QumuF!qq#1_qE`M4Yz- zYqS-uAhmh5#D;|@sq||W5O`#|l>Nc&7;S$rJ_YrzC2tDJf=%N`E}20w-9MQLJBe&o zuQIqLmyf-GE?tVAE|VPQw^_n~p-|Yy8rc?XVec2RN6SnJr+4q8mq6|uw zxy3Z6=}8<9yP2EzDwUV%X1=w8f+~=~RVE(6%c$%k;}+frK{E#s@^>|RkjqyR_SsDD z7~d@Df;ue$df1rGsWE%Ydff3+PzpW>e4cl10EN%(nv$((67o zuyhhC#-_u=@wnf*waXzFz4f6eNDgg#7|d;r00D#Ae{gTEzXwbQqJv*Pu10Y#s4C*S}TYq^1lFsx_<0XZ8wd{Ifb6xo_r>iYGhkI5f^~Zh>qmgxQD>0PRly zY}r>c9ZXrBZPLWe@0E29c^myc7A%}kfH|zB%|h*&rVv>WiT8u+T{+B}q9HFhXxPS< zWrreQkN;rAERx;hB%Fq}kafz5XL+A@Jkx?BVTg^55F2NH-^-W;G}wzR6m7+oLz@4g zB}nJqxl^m9oL83__^J}t)0^pKh@EgxG(7kRt{-g?`k`C_$)tdfbSc?xA@=H=HL#5n zJ;g*9y=m=k8Ltluk0hRW zI=kiquV(CAa16dH)YKWON_J+l#kJYB+~m?jw%x5@Ay?3E^Ygu-CDV2T94lq4+)*Fj zzd;g{5(JJtfO*-+N#%W6XgZrtbp(Hb|)nix1{$eVDRgST0({ z;&)HVj?G-XyC{O!pR1k_BKXyuR5)k>$KwD?Yb|2D&%L0kd@FvN=VGSOPek<@;x(aX z5s{0eN2}5buRDFSk0!L|A4V6L9H@jqQ)tqzT^S-r7xHybQ`oGRk)SkNHfZ|m(L)4Z z&(+k{)s*GUPk4hg>Lpz5$Vh~xNBv@lY7V@@nRTI{oIc&UYvb)=_+4JEiDOOHQ$W}% zkTH>~sh$a2U*MswcM9_DHzIa2p?qX-`fQXzT0im8g#yRQ7x9aaJ%)DH~0(n3cTbll}O++Dl7% zKEaW<0_`zAUQ=brDOuPLyEVt-pK=#u7fQ*+ow)?}IV_JIj2S#X2E+Go=aUf6gjB_K zos4m(_-FwIVbS&UxmCkk(uX5&Genx*mSSCp6nblNX^nmQW4*>BLqF*B{ACU+KGw_W zH9uW{AvDo8sVsh8NfUP6#ai0}0^s>rwbY;fU0*E3Tv^0!XHi6$RSHwIK}yd}=?9SJ zO3kAOwU6J#tnM6vcsd0UKBFx1d?wt)Dys>3EJ~NKM1# zo7h(!T?{;_lY`*9d!2A%p(Z{vdsx6ov;>QCPTtY0Q?gaWh#c{`UYygWD)rorw`}9E zjEd{GTH6ugf5fj3T`rsUB6hnDm(m>O5*Ojw?WU@49bKBcPdYS#LlV08L8#wXn1=5= zAKtC=kJWIRk_&+Q^CeGNL`?I>nBg9zacT$ef|~@~Bls2^?+6%H2&(jz0~RFx-H`8g zh*~?};F(b=t_vW~Y$oO+sIz5$W%*!p=pkqLCFoqAZ(`JHqje~nL@)@o_u!I!9^T3u z`zd-DD#vI`x%@|4QnfZrtk?E16#gW`=>^43MRbH$)3(EvO38vvnc;(;X;S?E5*o z^@BV0!8$19&VWtv0`kp%eB9$Y>sOniE*>*4j#+Rf`m7#__E@4U0Ket=$W0<`_nG#l za(BmRR&N#ZRnFOHurzJlGac?IIv5Jtc?|gblT;_;& zNnws}Fz%~+^wm>A)y5xPLu+bo)RRq~bA^*jdmeK}&f(dEqNP0)ZAs{ab_V^;7m#YU zKW&#>C*27$Qg%2egQ?NHTllAn0EadZwYAZD%Yphti@rvqzKu<@&&}ooG&85HWxws3 z3vyzTY-i49bc36}%RIQ=KUxBfqF9nz(> zTp0c83<0{(P>8z~fp#1%31?!`&zcN&n4Q@=QK&mT@oY)RE84KIOIdcj>aXYq71V&q zlqv$#vdtr&hXK7s)6!=$gQiN2&zi`P4Z{95(2B{k!S|;RYDyhX5Q)MkFc4c_!j^Lw zqv0)G0K1@){aoVVMDI0qF0jY5GGRPGk+(Vuw$oqN(5GIFz1S7e>JJLZaq`1HhxrV5 zJis9j1M>tF`Mx^8SQBtLDFg8k>#jwDiQUe>J7i$h6?Qdp=^?yZq89b|Q5_W}e`^L< zstmRr-|XvzB~P&AE-8MyV0*?n{G6$MeMQMGRJ%8)uIuD6qJ;33Jr^;IVj@-14!Gr< z`N^*N59jtpC*V~ZgWd?_>BfBr66S<^;tKg>E1wYlQ;#D_vT%+yjN)_t(ot z9_?0#%5TixwHUm8YpOYjEAim>>(%&aDZk#=-bYk^_jLcJC(?WKDFJXtGf{|lIu`oI z$Z421PFie|Xn4w+TLo+*?c|?>0qtU!{Jbw~@GNGs`9N1}-D63Nsjqm)7H=+h^(x=CShwVK0wuOB)Oc|Lr~9%Q$4_^QzZu2h&JAew#WY3_KLw5;obKJJR|I^#m8BEC4bR8oPX=wbKfwKT-{F&gWzkTaLz2@zOM zSMGuijR=uWK7PZ__9m*B1#&Q%WdOX?)~X2BlNWde7=sDdD?BcVNqJ=0p@6A>bM2WO zew>#L?g+S>^>F(P3QEwhPjOgi*wlNiZC)}MM!#s)ugV2o=@+BOzu$1s>iN8AT`g}= z-6o($Ck74B_`7g2-Ok)>7GNzIo za?8^2oamL`n?*Zcqt73@3v=7+6I}1&)oCD@HAa9=SuWRc_r=Fj94w!tH3?joEq?DY zGXfjg&k7dv_5SWq{pRF97>zKmupU-DYIHAxP`b<#UZORrj|%Jhuz$aCK4tOajz}%$ z(2?WcIXzA-kGR#Eb)Ye-DYL^dZ^6KVb{D;Bm}_fxuPdz)dtN|CeBYR-Pn{O@m!KP^ z1WdHSh|7oi#vpZj!CQYk_Xn!LU)g-i1YfT6k=neYCsnU`?ej zz?~!2?s@ZY%b|E|U9_ItQFH)|Kbg$MK;N2anX5J0O8>m^^n6+K$_o)e4eJ=TeDDX^ zKwX{p8Qv~LDU+7m^L!gk*GkF6NK;)XYx6Lg``ba{`tPk~J-)|8o6^tI?wOP%$ z0=coNoB~T2S_nFC<#kHl1|EuhJ<;5UfSv1HW@y>BBLU6^xbf~?*hu$X4)1uEJHdVx z7qhNdXn};1w-J}v>x9X(CdG2{fB3_Mk3z5C$sq2rTxxS#9SH~8Z%h!irs`~b4oF|A zO*-6Tm6ZD;I|Yy1>PHvqZ))_a2VL=rC8VY2@PmJfgVh8%a~bOF`~mNB7F_ukk2C+=B>7|g2Q zld49ewxJ$sxs?~0o`{t>Y%NDQ}5>j&Ry{_n;IT;0TB9L?$#x^ zT0g!%yXI{YwmSv!P`?=dLRbW*IOY1FKi&H`oVGto2dV@Q<6^#EAS<(FNr?geI@ zIy+h0EgNWb35p@mjJUr(tprH=>i89+AQ5(b{4MjW%*FHaA0N0CXQS3YmI$Q#XfNX6|8O-`$Y-*ri1*0<*lTu72_QQLLiKT)%T^E4 zj%H^Z*dDGXF?x~-&F=S?(s0q)#%$A`DkZH1b9UDwJHW+%=r3Gn4||)wF1njVd4w~k zI|mOxO-5%aEW)4dc-Ok-i|eV;_#}pqiD&Hw!rd&=j%RpIO)EfP;7GC)e0ID~$okuA zu#uEoAqvn&1AYI{tSOyp&!QwWQK{pb-=r+phTwRA1Uhn!|sA>sw9sB zvSUA~uTB6ii92hzSK3Fp%BX`-3c1_)u=lNWe^Pf&4P|-qd)(n*D`BNik<$G|8uJ{{ z7a-Li{GZS7iFvqXd~uXeH)<^iEh3cat4UT%jFa;s6G0m*gr!o!LLla;57{c^Bbtbg zZspZMnZ~QJVVCP~z5$6;_f>SA0$%p-A?K8W1SCjo>9l~=<;9}6Ws~a}U|ags++faZ zTF%yA@Et+(=bDefxL(E=lJp8Vy{3PHB{;pQQmhqrSBPJ{`nquglJB)__UN7-yj(po zBgO@=vTnuunwbE@0L&^a`*`i9;42xpMB|krf z|Gg!X-0$!DIPK92d?fd{M@qjZ(($VH^w%OOdR9n$wS8}@J^WR<)(1OLKw3hOG=N5fAY>!7bAZRrr02L0*slU$ znj$!7c`{7vw0|VY-<(PWPoWLI6y9+7DLkX}Px189a6+LFX@y$mA@^_YxO&+W#`>vR z(1zb28vhxVth_Q%yNc;%2!Fum%hQ7zoiLTJgMh0oYf8qia5t--dx4balLdjp2c zW9GMg-FN)9zdmYdJ=~o+2x;VOWpS66&$MhK8j1xUgh|g)2sM4!@e7?f!g?!HTB{$a zR%WWOZi8;LcC=m`71w}C_Yb-r56j_^SkS*a$Me5;>-!Zrb!$xs;UYNqgZ@XC|5%i% znUaG4(adGk%E4T9K~=*EQn$$4ilXVP38^>EM>b*Wr=(-!&TMR%Wnkl?xqGXfYZR9U z(y;Dven7K#>TfZLL9(C8KbfJ{3z8kn(HMj}bP-O#FD`8HM8DPRtT|xmFu|3UkU(CQ zeljc)or70i|7PfJ5?F1Rkyr&_=$OQ8%LUw{YGV{2Kewpc&0_s?zpTgcnd$2pmo=&)5ZRkCw&2m%sqxcH?kEZW63jcPpMo%b)e^c zxnH@<3l;KQ)~XRI#Hj48%BdPrX)h{Npm@gQJ#YZK&nQO@J_Q{1o_vvn0R*!f zzzNKq-RjtLtPjhoTA)h)1A7!TIzczcvte;`P<76SluVIdV!>@{S`w0hj z`MwscZ7B`2E_m*Cij_KSuUS<6L!b!8-u3Brkf`G0&t^KnQX)DOMSST9HWoj{TxGgq zrW{wZ^nV%hV>1c-eP87`=(e4NklO3otA2CT^rZxkAiQyifO?IMwPE$$dpK(q-k06( zG>eqMb1%e6v|z3jIt^xB`?D_N;-kiTld?n;Bu@>)P(0{!+hR?KkD-BaXInecce z;sk}O^}j=l?Mu$-+84f?wF-5dDJ2a^LWrQTZjv|pryR|C)A1ADTx=_aa@)jp2+lWq zRJ)8VmJ7u+$5+3waopZ18`V&(Fx4-=>!~1jJG=HR+4rL?&PHair=vlg^a{1Y0>c*9 zXM9C5bw1l}l?E_p6nUXONyO*`4do66fI%ftbf+;ftkFj z)?8C{HRi(qVkgf8Njk}@{8DMWlC`sSJ8*FyWvq%8?p+S0G%^*<$eg3`yE5a}`%fr2 zKg@y82T(Vbgk&?bGpfa0%0b$1{)JMA!JzqNk>UIiZwX_LTkA3m=bkG@gyi;s@yAQD z{wGd!EI~qNnQZ3u_A5+Aa{R&@T-zn>Vz4|Srx?AGgi=#Xm{^5zG4`Zt>fx2p$Wp+( z^EQ#0!XdZ2uD*wPpph->%cqP5~9!!Ol@(aCj4jV}ZqZ55IB^F)rWnZc+*=o)ZD*P4Os} zZgsYGmBDohUhR(zzZ(hr8;PUz-oC{Bp--~tZ9SNdBg<{rn4Fy!s%g0f=gD6cWcP7m zHBpetgQ|ioPB)5qY9X%2(f<5cW{4Q;a_wCk4%n{s`f5NP*VT_!xJA3)-DBpKO&mVk zn>#_t@slb36jZd;ic?^`TIpKN)y1VOMliKw4t+6PD}6CxgB$070XU!x`tFAv@U6?v zr0|GauJkFlGqFUZ-kvxYMyC?X~ zqNjXq?yjL&-G@w8mNj)lzE|3)a=CNt*rIJDm*@uY&E}!lQ2SZvfdbQrqF~rNJ@<9v z8{9c)8bF)wy|*gy?Sj_)do=5`D)RlvG2@IkSrZYD@UPTj&5^r#%! zUx9pGQO%=20Lp-GvOs} zo-g1CT#mg>FVod7KX!f5f*d(C@&uB;+)bPWFB|}PtH0{hP?}gR^Y)y(d5BWz%3Q&a z0b1wG9{H;)4xzpM(qh8bcJLl*p`(%@g2(wB1+t7!58{0huqheokw!p?t!?y~f1q%y z63B~hBJp0R&N+9C(9)Xag_!{sqH0%CR^WRF{F@3(?A@rzdpfveaC}Jakzg{n*dwpW z-?)>!&iNg1uE~Cxrh#}{)UVO$*`kZ#gpo`D#kRuBj`iYp&wTqcuT=k_`cp((Fb~!f z=Ip&?Jk>> _taskExpandoObjects = new(); + private readonly ConcurrentDictionary>> _taskJsonObjects = new(); + + public ConfigurationApiClient( + IServiceProvider serviceProvider, + IMemoryCacheClient client, + JsonSerializerOptions jsonSerializerOptions, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + : base(defaultSectionOption, expandSectionOptions) + { + _serviceProvider = serviceProvider; + _client = client; + _jsonSerializerOptions = jsonSerializerOptions; + } + + public Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawAsync(string environment, string cluster, string appId, + string configObject, Action valueChanged) + { + var key = FomartKey(environment, cluster, appId, configObject); + return GetRawByKeyAsync(key, valueChanged); + } + + public async Task GetAsync(string environment, string cluster, string appId, string configObject, Action valueChanged) + { + var key = FomartKey(environment, cluster, appId, configObject); + + var value = await _taskJsonObjects.GetOrAdd(key, (k) => new Lazy>(async () => + { + var options = new JsonSerializerOptions(_jsonSerializerOptions); + options.EnableDynamicTypes(); + + var result = await GetRawByKeyAsync(k, (value) => + { + var result = JsonSerializer.Deserialize(value, options); + + var newValue = new Lazy>(() => Task.FromResult((object)result!)); + _taskJsonObjects.AddOrUpdate(k, newValue, (_, _) => newValue); + valueChanged?.Invoke(result!); + }); + if (typeof(T).GetInterfaces().Any(type => type == typeof(IConvertible))) + { + if (result.ConfigurationType == ConfigurationTypes.Text) + return Convert.ChangeType(result.Raw, typeof(T)); + + throw new FormatException(result.Raw); + } + + return JsonSerializer.Deserialize(result.Raw, options) ?? throw new ArgumentException(nameof(configObject)); + })).Value; + + return (T)value; + } + + public async Task GetDynamicAsync(string environment, string cluster, string appId, string configObject, + Action valueChanged) + { + var key = FomartKey(environment, cluster, appId, configObject); + + var value = _taskExpandoObjects.GetOrAdd(key, (k) => new Lazy>(async () => + { + var options = new JsonSerializerOptions(_jsonSerializerOptions); + options.EnableDynamicTypes(); + + var raw = await GetRawByKeyAsync(k, (value) => + { + var result = JsonSerializer.Deserialize(value, options); + var newValue = new Lazy>(() => Task.FromResult(result)!); + _taskExpandoObjects.AddOrUpdate(k, newValue!, (_, _) => newValue!); + valueChanged?.Invoke(result!); + }); + + return JsonSerializer.Deserialize(raw.Raw, options) ?? throw new ArgumentException(nameof(configObject)); + })).Value; + + return await value; + } + + public async Task GetDynamicAsync(string key) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentNullException(nameof(key)); + + var configuration = _serviceProvider.GetRequiredService(); + key = key.Replace(".", ConfigurationPath.KeyDelimiter); + return await Task.FromResult(Format(configuration.GetSection(key))); + } + + private async Task<(string Raw, ConfigurationTypes ConfigurationType)> GetRawByKeyAsync(string key, Action valueChanged) + { + var raw = await _client.GetAsync(key, (value) => + { + var result = FormatRaw(value); + valueChanged?.Invoke(result.Raw); + }); + + return FormatRaw(raw); + } + + private (string Raw, ConfigurationTypes ConfigurationType) FormatRaw(string? raw) + { + if (raw == null) + throw new ArgumentException("configObject invalid"); + + var result = JsonSerializer.Deserialize(raw, _jsonSerializerOptions); + if (result == null || result.ConfigFormat == 0) + throw new ArgumentException("configObject invalid"); + + switch (result.ConfigFormat) + { + case ConfigFormats.Json: + return (result.Content!, ConfigurationTypes.Json); + + case ConfigFormats.Text: + return (result.Content!, ConfigurationTypes.Text); + + case ConfigFormats.Properties: + var properties = PropertyConfigurationParser.Parse(result.Content!, _jsonSerializerOptions); + if (properties == null) + throw new ArgumentException("configObject invalid"); + + return (JsonSerializer.Serialize(properties, _jsonSerializerOptions), ConfigurationTypes.Properties); + + default: + throw new NotSupportedException("Unsupported configuration type"); + } + } + + private string FomartKey(string environment, string cluster, string appId, string configObject) + => $"{GetEnvironment(environment)}-{GetCluster(cluster)}-{GetAppId(appId)}-{GetConfigObject(configObject)}".ToLower(); + + private dynamic Format(IConfigurationSection section) + { + var childrenSections = section.GetChildren(); + if (!section.Exists() || !childrenSections.Any()) + { + return section.Value; + } + else + { + var result = new ExpandoObject(); + var parent = result as IDictionary; + foreach (var child in childrenSections) + { + parent[child.Key] = Format(child); + } + return result; + } + } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs new file mode 100644 index 000000000..f5702442f --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/ConfigurationApiManage.cs @@ -0,0 +1,28 @@ +namespace Masa.Contrib.BasicAbility.Dcc; + +public class ConfigurationApiManage : ConfigurationAPIBase, IConfigurationApiManage +{ + private readonly ICallerProvider _callerProvider; + + public ConfigurationApiManage( + ICallerProvider callerProvider, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + : base(defaultSectionOption, expandSectionOptions) + { + _callerProvider = callerProvider; + } + + public async Task UpdateAsync(string environment, string cluster, string appId, string configObject, object value) + { + var requestUri = $"open-api/releasing/{GetEnvironment(environment)}/{GetCluster(cluster)}/{GetAppId(appId)}/{GetConfigObject(configObject)}?secret={GetSecret(appId)}"; + var result = await _callerProvider.PutAsync(requestUri, value, default); + + // 299 is the status code when throwing a UserFriendlyException in masa.framework + if ((int)result.StatusCode == 299 || !result.IsSuccessStatusCode) + { + var error = await result.Content.ReadAsStringAsync(); + throw new HttpRequestException(error); + } + } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs new file mode 100644 index 000000000..27d112751 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigFormats.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal; + +internal enum ConfigFormats +{ + Properties = 1, + Text, + Json +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs new file mode 100644 index 000000000..6dde24188 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/ConfigurationAPIBase.cs @@ -0,0 +1,37 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal; + +public class ConfigurationAPIBase +{ + private readonly DccSectionOptions _defaultSectionOption; + private readonly List _expandSectionOptions; + + protected ConfigurationAPIBase(DccSectionOptions defaultSectionOption, List? expandSectionOptions) + { + _defaultSectionOption = defaultSectionOption; + _expandSectionOptions = expandSectionOptions ?? new(); + } + + protected string GetSecret(string appId) + { + if (_defaultSectionOption.AppId == GetAppId(appId)) + return _defaultSectionOption.Secret ?? ""; + + var option = _expandSectionOptions.FirstOrDefault(x => x.AppId == appId); + if (option == null) + throw new ArgumentNullException(nameof(appId)); + + return option.Secret ?? ""; + } + + protected string GetEnvironment(string environment) + => !string.IsNullOrEmpty(environment) ? environment : _defaultSectionOption.Environment!; + + protected string GetCluster(string cluster) + => !string.IsNullOrEmpty(cluster) ? cluster : _defaultSectionOption.Cluster!; + + protected string GetAppId(string appId) + => !string.IsNullOrEmpty(appId) ? appId : _defaultSectionOption.AppId!; + + protected string GetConfigObject(string configObject) + => !string.IsNullOrEmpty(configObject) ? configObject : throw new ArgumentNullException(nameof(configObject)); +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Constants.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Constants.cs new file mode 100644 index 000000000..8b4f30645 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Constants.cs @@ -0,0 +1,12 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal; + +internal class Constants +{ + internal const string DEFAULT_CLIENT_NAME = "masa.plugins.caching.dcc"; + + internal const string DEFAULT_SUBSCRIBE_KEY_PREFIX = "masa.dcc:"; + + internal const string DEFAULT_ENVIRONMENT_NAME = "ASPNETCORE_ENVIRONMENT"; + + internal const string DATA_DICTIONARY_SECTION_NAME = "DataDictionary"; +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs new file mode 100644 index 000000000..a450c0012 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccConfigurationRepository.cs @@ -0,0 +1,92 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal; + +internal class DccConfigurationRepository : AbstractConfigurationRepository +{ + private readonly IConfigurationApiClient _client; + + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; + + private readonly ConcurrentDictionary> _dictionaries = new(); + + private readonly ConcurrentDictionary _configObjectConfigurationTypeRelations = new(); + + public DccConfigurationRepository( + IEnumerable sectionOptions, + IConfigurationApiClient client, + ILoggerFactory loggerFactory) + : base(loggerFactory) + { + _client = client; + foreach (var sectionOption in sectionOptions) + { + Initialize(sectionOption).ConfigureAwait(false).GetAwaiter().GetResult(); + } + } + + private async Task Initialize(DccSectionOptions sectionOption) + { + foreach (var configObject in sectionOption.ConfigObjects) + { + string key = $"{sectionOption.Environment!}-{sectionOption.Cluster!}-{sectionOption.AppId}-{configObject}".ToLower(); + var result = await _client.GetRawAsync(sectionOption.Environment!, sectionOption.Cluster!, sectionOption.AppId, configObject, (val) => + { + if (_configObjectConfigurationTypeRelations.TryGetValue(key, out var configurationType)) + { + _dictionaries[key] = FormatRaw(sectionOption.AppId, configObject, val, configurationType); + FireRepositoryChange(SectionType, Load()); + } + }); + + _configObjectConfigurationTypeRelations.TryAdd(key, result.ConfigurationType); + _dictionaries[key] = FormatRaw(sectionOption.AppId, configObject, result.Raw, result.ConfigurationType); + } + } + + private IDictionary FormatRaw(string appId, string configObject, string? raw, ConfigurationTypes configurationType) + { + if (raw == null) + return new Dictionary(); + + switch (configurationType) + { + case ConfigurationTypes.Json: + return SecondaryFormat(appId, configObject, JsonConfigurationParser.Parse(raw)); + case ConfigurationTypes.Properties: + return SecondaryFormat(appId, configObject, JsonSerializer.Deserialize>(raw)!); + case ConfigurationTypes.Text: + return new Dictionary() + { + { $"{appId}{ConfigurationPath.KeyDelimiter}{DATA_DICTIONARY_SECTION_NAME}{ConfigurationPath.KeyDelimiter}{configObject}" , raw ?? "" } + }; + default: + throw new NotSupportedException(nameof(configurationType)); + } + } + + private IDictionary SecondaryFormat( + string appId, + string configObject, + IDictionary data) + { + var dictionary = new Dictionary(); + foreach (var item in data) + { + dictionary[$"{appId}{ConfigurationPath.KeyDelimiter}{configObject}{ConfigurationPath.KeyDelimiter}{item.Key}"] = item.Value; + } + return dictionary; + } + + public override Properties Load() + { + Dictionary properties = new(); + foreach (var item in _dictionaries) + { + foreach (var key in item.Value.Keys) + { + properties[key] = item.Value[key] ?? string.Empty; + } + } + return new Properties(properties); + } + +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs new file mode 100644 index 000000000..55a2d6326 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/DccFactory.cs @@ -0,0 +1,20 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal; + +internal class DccFactory +{ + public static IConfigurationApiClient CreateClient( + IServiceProvider serviceProvider, + IMemoryCacheClient client, + JsonSerializerOptions jsonSerializerOptions, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + { + return new ConfigurationApiClient(serviceProvider, client, jsonSerializerOptions, defaultSectionOption, expandSectionOptions); + } + + public static IConfigurationApiManage CreateManage( + ICallerFactory callerFactory, + DccSectionOptions defaultSectionOption, + List? expandSectionOptions) + => new ConfigurationApiManage(callerFactory.CreateClient(), defaultSectionOption, expandSectionOptions); +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs new file mode 100644 index 000000000..96e1420dc --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/Property.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.BasicAbility.Dcc.Internal.Model; + +internal class Property +{ + public string Key { get; set; } = default!; + + public string Value { get; set; } = default!; +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs new file mode 100644 index 000000000..c21311b9d --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Model/PublishRelease.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.BasicAbility.Dcc.Internal.Model; + +internal class PublishRelease +{ + public ConfigFormats ConfigFormat { get; set; } + + public string? Content { get; set; } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs new file mode 100644 index 000000000..e43482ff1 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/JsonConfigurationParser.cs @@ -0,0 +1,101 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal.Parser; + +/// +/// https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Configuration.Json/src/JsonConfigurationFileParser.cs +/// +internal sealed class JsonConfigurationParser +{ + private JsonConfigurationParser() + { + } + + private readonly Dictionary _data = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Stack _paths = new Stack(); + + public static IDictionary Parse(string json) + => new JsonConfigurationParser().ParseJson(json); + + private IDictionary ParseJson(string json) + { + var jsonDocumentOptions = new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + var doc = JsonDocument.Parse(json, jsonDocumentOptions); + + if (doc.RootElement.ValueKind != JsonValueKind.Object) + { + throw new FormatException($"[{doc.RootElement.ValueKind}]Invalid top level JsonElement."); + } + + VisitElement(doc.RootElement); + + return _data; + } + + private void VisitElement(JsonElement element) + { + var isEmpty = true; + + foreach (JsonProperty property in element.EnumerateObject()) + { + isEmpty = false; + EnterContext(property.Name); + VisitValue(property.Value); + ExitContext(); + } + + if (isEmpty && _paths.Count > 0) + { + _data[_paths.Peek()] = ""; + } + } + + private void VisitValue(JsonElement value) + { + Debug.Assert(_paths.Count > 0); + + switch (value.ValueKind) + { + case JsonValueKind.Object: + VisitElement(value); + break; + + case JsonValueKind.Array: + int index = 0; + foreach (JsonElement arrayElement in value.EnumerateArray()) + { + EnterContext(index.ToString()); + VisitValue(arrayElement); + ExitContext(); + index++; + } + + break; + + case JsonValueKind.Number: + case JsonValueKind.String: + case JsonValueKind.True: + case JsonValueKind.False: + case JsonValueKind.Null: + string key = _paths.Peek(); + if (_data.ContainsKey(key)) + { + throw new FormatException($"[{key}]key is duplicated."); + } + + _data[key] = value.ToString(); + break; + + default: + throw new FormatException($"[{value.ValueKind}]Unsupported json token."); + } + } + + private void EnterContext(string context) => + _paths.Push(_paths.Count > 0 ? _paths.Peek() + ConfigurationPath.KeyDelimiter + context : context); + + private void ExitContext() => _paths.Pop(); +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs new file mode 100644 index 000000000..457dbd6c7 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Internal/Parser/PropertyConfigurationParser.cs @@ -0,0 +1,7 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Internal.Parser; + +internal class PropertyConfigurationParser +{ + public static IDictionary? Parse(string raw, JsonSerializerOptions serializerOption) + => JsonSerializer.Deserialize>(raw, serializerOption)?.ToDictionary(k => k.Key, v => v.Value); +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Masa.Contrib.BasicAbility.Dcc.csproj b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Masa.Contrib.BasicAbility.Dcc.csproj new file mode 100644 index 000000000..a1c1843c1 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Masa.Contrib.BasicAbility.Dcc.csproj @@ -0,0 +1,23 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs new file mode 100644 index 000000000..49813a094 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/MasaConfigurationExtensions.cs @@ -0,0 +1,204 @@ +namespace Masa.Contrib.BasicAbility.Dcc; + +public static class MasaConfigurationExtensions +{ + public static IMasaConfigurationBuilder UseDcc( + this IMasaConfigurationBuilder builder, + IServiceCollection services, + Action? jsonSerializerOptions = null, + Action? callerOptions = null) + => builder.UseDcc(services, "Appsettings", jsonSerializerOptions, callerOptions); + + public static IMasaConfigurationBuilder UseDcc( + this IMasaConfigurationBuilder builder, + IServiceCollection services, + string defaultSectionName, + Action? jsonSerializerOptions = null, + Action? callerOptions = null) + { + if (!builder.GetSectionRelations().TryGetValue(defaultSectionName, out IConfiguration? configuration)) + throw new ArgumentNullException("Failed to obtain Dcc configuration, check whether the current section is configured with Dcc"); + + var configurationSection = configuration.GetSection("DccOptions"); + var dccOptions = configurationSection.Get(); + + List expandSections = new(); + var configurationExpandSection = configuration.GetSection("ExpandSections"); + if (configurationExpandSection.Exists()) + { + configurationExpandSection.Bind(expandSections); + } + + return builder.UseDcc(services, () => dccOptions, option => + { + option.Environment = configuration["Environment"]; + option.Cluster = configuration["Cluster"]; + option.AppId = configuration["AppId"]; + option.ConfigObjects = configuration.GetSection("ConfigObjects").Get>(); + option.Secret = configuration["Sectet"]; + }, option => option.ExpandSections = expandSections, jsonSerializerOptions, callerOptions); + } + + public static IMasaConfigurationBuilder UseDcc( + this IMasaConfigurationBuilder builder, + IServiceCollection services, + Func configureOptions, + Action defaultSectionOptions, + Action? expansionSectionOptions = null, + Action? jsonSerializerOptions = null, + Action? callerOptions = null) + { + if (services.Any(service => service.ImplementationType == typeof(DccConfigurationProvider))) + return builder; + + services.AddSingleton(); + + var config = GetDccConfigurationOption(configureOptions, defaultSectionOptions, expansionSectionOptions); + + var jsonSerializerOption = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + jsonSerializerOptions?.Invoke(jsonSerializerOption); + services.AddCaller(options => + { + if (callerOptions == null) + { + options.UseHttpClient(() + => new MasaHttpClientBuilder(DEFAULT_CLIENT_NAME, string.Empty, opt => opt.BaseAddress = new Uri(config.DccConfigurationOption.ManageServiceAddress)) + ); + } + else + { + callerOptions.Invoke(options); + } + }); + + services.AddMasaRedisCache(DEFAULT_CLIENT_NAME, config.DccConfigurationOption.RedisOptions).AddSharedMasaMemoryCache(config.DccConfigurationOption.SubscribeKeyPrefix ?? DEFAULT_SUBSCRIBE_KEY_PREFIX); + + TryAddConfigurationApiClient(services, config.DefaultSectionOption, config.ExpansionSectionOptions, jsonSerializerOption); + TryAddConfigurationApiManage(services, config.DefaultSectionOption, config.ExpansionSectionOptions); + + var sectionOptions = new List() + { + config.DefaultSectionOption + }.Concat(config.ExpansionSectionOptions); + + var configurationApiClient = services.BuildServiceProvider().GetRequiredService(); + var loggerFactory = services.BuildServiceProvider().GetRequiredService(); + builder.AddRepository(new DccConfigurationRepository(sectionOptions, configurationApiClient, loggerFactory)); + return builder; + } + + public static IServiceCollection TryAddConfigurationApiClient(IServiceCollection services, + DccSectionOptions defaultSectionOption, + List expansionSectionOptions, + JsonSerializerOptions jsonSerializerOption) + { + services.TryAddSingleton(serviceProvider => + { + var client = serviceProvider.GetRequiredService() + .CreateClient(DEFAULT_CLIENT_NAME); + + if (client == null) + throw new ArgumentNullException(nameof(client)); + + return DccFactory.CreateClient( + serviceProvider, + client, + jsonSerializerOption, + defaultSectionOption, + expansionSectionOptions); + }); + return services; + } + + public static IServiceCollection TryAddConfigurationApiManage(IServiceCollection services, + DccSectionOptions defaultSectionOption, + List expansionSectionOptions) + { + services.TryAddSingleton(serviceProvider => + { + var callerFactory = serviceProvider.GetRequiredService(); + return DccFactory.CreateManage(callerFactory, defaultSectionOption, expansionSectionOptions); + }); + return services; + } + + private static (DccSectionOptions DefaultSectionOption, List ExpansionSectionOptions, DccConfigurationOptions DccConfigurationOption) GetDccConfigurationOption( + Func configureOptions, + Action defaultSectionOptions, + Action? expansionSectionOptions = null) + { + var dccConfigurationOption = configureOptions(); + if (dccConfigurationOption == null) + throw new ArgumentNullException(nameof(configureOptions)); + + if (string.IsNullOrEmpty(dccConfigurationOption.ManageServiceAddress)) + throw new ArgumentNullException(nameof(dccConfigurationOption.ManageServiceAddress)); + + if (dccConfigurationOption.RedisOptions == null) + throw new ArgumentNullException(nameof(dccConfigurationOption.RedisOptions)); + + if (dccConfigurationOption.RedisOptions.Servers == null || dccConfigurationOption.RedisOptions.Servers.Count == 0 || dccConfigurationOption.RedisOptions.Servers.Any(service => string.IsNullOrEmpty(service.Host) || service.Port <= 0)) + throw new ArgumentNullException(nameof(dccConfigurationOption.RedisOptions.Servers)); + + if (defaultSectionOptions == null) + throw new ArgumentNullException(nameof(defaultSectionOptions)); + + var defaultSectionOption = new DccSectionOptions(); + defaultSectionOptions.Invoke(defaultSectionOption); + + if (string.IsNullOrEmpty(defaultSectionOption.AppId)) + throw new ArgumentNullException("AppId cannot be empty"); + + if (defaultSectionOption.ConfigObjects == null || !defaultSectionOption.ConfigObjects.Any()) + throw new ArgumentNullException("ConfigObjects cannot be empty"); + + if (string.IsNullOrEmpty(defaultSectionOption.Cluster)) + defaultSectionOption.Cluster = "Default"; + if (string.IsNullOrEmpty(defaultSectionOption.Environment)) + defaultSectionOption.Environment = GetDefaultEnvironment(); + + var dccCachingOption = new DccExpandSectionOptions(); + expansionSectionOptions?.Invoke(dccCachingOption); + List expansionOptions = new(); + foreach (var expansionOption in dccCachingOption.ExpandSections ?? new()) + { + if (string.IsNullOrEmpty(expansionOption.Environment)) + expansionOption.Environment = defaultSectionOption.Environment; + if (string.IsNullOrEmpty(expansionOption.Cluster)) + expansionOption.Cluster = defaultSectionOption.Cluster; + + if (expansionOption.ConfigObjects == null || !expansionOption.ConfigObjects.Any()) + throw new ArgumentNullException("ConfigObjects in the extension section cannot be empty"); + + if (expansionOption.AppId == defaultSectionOption.AppId || + expansionOptions.Any(section => section.AppId == expansionOption.AppId)) + throw new ArgumentNullException("The current section already exists, no need to mount repeatedly"); + + expansionOptions.Add(expansionOption); + } + return (defaultSectionOption, expansionOptions, dccConfigurationOption); + } + + private static ICachingBuilder AddSharedMasaMemoryCache(this ICachingBuilder builder, string subscribeKeyPrefix) + { + builder.AddMasaMemoryCache(options => + { + options.SubscribeKeyType = SubscribeKeyTypes.SpecificPrefix; + options.SubscribeKeyPrefix = subscribeKeyPrefix; + }); + + return builder; + } + + private static string GetDefaultEnvironment() + => System.Environment.GetEnvironmentVariable(DEFAULT_ENVIRONMENT_NAME) ?? + throw new ArgumentNullException("Error getting environment information, please make sure the value of ASPNETCORE_ENVIRONMENT has been configured"); + + private class DccConfigurationProvider + { + + } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs new file mode 100644 index 000000000..f1f21e7ca --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccConfigurationOptions.cs @@ -0,0 +1,13 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Options; + +public class DccConfigurationOptions +{ + public RedisConfigurationOptions RedisOptions { get; set; } + + public string ManageServiceAddress { get; set; } = default!; + + /// + /// The prefix of Dcc PubSub, it is not recommended to modify + /// + public string? SubscribeKeyPrefix { get; set; } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs new file mode 100644 index 000000000..20a5bd0e5 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccExpandSectionOptions.cs @@ -0,0 +1,9 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Options; + +public class DccExpandSectionOptions +{ + /// + /// Expansion section information + /// + public List? ExpandSections { get; set; } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs new file mode 100644 index 000000000..953c128e6 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/Options/DccSectionOptions.cs @@ -0,0 +1,24 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Options; + +public class DccSectionOptions +{ + /// + /// The environment name. + /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty + /// + public string? Environment { get; set; } = null; + + /// + /// The cluster name. + /// + public string? Cluster { get; set; } + + /// + /// The app id. + /// + public string AppId { get; set; } = default!; + + public List ConfigObjects { get; set; } = default!; + + public string? Secret { get; set; } +} diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.md b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.md new file mode 100644 index 000000000..a31af4dc3 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.md @@ -0,0 +1,164 @@ +[ไธญ](README.zh-CN.md) | EN + +## Masa.Contrib.BasicAbility.Dcc + +Effect: + +Extend the ability of IConfiguration to manage remote configuration through Dcc. + +```c# +IConfiguration +โ”œโ”€โ”€ Local Local node (fixed) +โ”œโ”€โ”€ ConfigurationAPI Remote node (fixed Dcc to expand its capacity) +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms Custom node +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name Parameter Name +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary Dictionary (fixed) The type of Text in DCC is mounted here +``` + +Example๏ผš + +```C# +Install-Package Masa.Contrib.Configuration +Install-Package Masa.Contrib.BasicAbility.Dcc //Provides the ability to remotely configure +``` + +appsettings.json +``` +{ + //Dcc configuration, extended Configuration capabilities, support remote configuration + "DccOptions": { + "ManageServiceAddress": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "Environment": "Development", + "ConfigObjects": [ "Platforms" ], //The name of the object to be mounted, the Platforms configuration will be mounted here under the ConfigurationAPI: node + "Secret": "", //Dcc App key + "Cluster": "Default" +} + +``` + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + configurationBuilder.UseDcc(builder.Services);//Use Dcc + + options.Mapping(SectionTypes.Local, "Appsettings", ""); //Map CustomDccSectionOptions to the Appsettings node under Local +}); + +/// +/// Automatically map node relationships +/// +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; + + [JsonIgnore] + public virtual string? ParentSection { get; init; } = "AppId"; + + [JsonIgnore] + public virtual string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +public class CustomDccSectionOptions +{ + /// + /// The environment name. + /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty + /// + public string? Environment { get; set; } = null; + + /// + /// The cluster name. + /// + public string? Cluster { get; set; } + + /// + /// The app id. + /// + public string AppId { get; set; } = default!; + + public List ConfigObjects { get; set; } = default!; + + public string? Secret { get; set; } +} +``` + +How to use configuration๏ผš + +```c# +var app = builder.Build(); + +app.MapGet("/GetPlatform", ([FromServices] IOptions option) => +{ + //recommend + return System.Text.Json.JsonSerializer.Serialize(option.Value);//Or use IOptionsMonitor to support monitoring changes +}); + +app.MapGet("/GetPlatformByMonitor", ([FromServices] IOptionsMonitor options) => +{ + options.OnChange(option => + { + //TODO Configuration update + }); + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +}); + +app.MapGet("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //Format ConfigurationAPI::: + return configuration["ConfigurationAPI::Platforms:Name"]; +}); + +app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, + [FromServices] IOptions configuration, + PlatformOptions newPlatform) => +{ + //Modify Dcc configuration + return configurationAPIManage.UpdateAsync(option.Value.Environment, + option.Value.Cluster, + option.Value.AppId, + "",newPlatform);//Here Replace-With-Your-ConfigObject is Platforms +}); +app.Run(); +``` + +How to update the configuration: + +```c# +var app = builder.Build(); + +app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, + [FromServices] IOptions configuration, + PlatformOptions newPlatform) => +{ + //Modify Dcc configuration + return configurationAPIManage.UpdateAsync(option.Value.Environment, + option.Value.Cluster, + option.Value.AppId, + "" + ,newPlatform); + //Here Replace-With-Your-ConfigObject is Platforms +}); + +app.Run(); +``` + +Summarize๏ผš + +Dcc provides remote configuration management and viewing capabilities for IConfiguration. For the complete capabilities of IConfiguration, please refer to the [document](../../Configuration/Masa.Contrib.Configuration/README.md) + +Platforms here is remote configuration, which introduces the effect and usage of remote configuration after mounting to IConfiguration. This configuration has nothing to do with Platforms in Masa.Contrib.Configuration. It just shows the use of the same configuration information in two sources. Ways and differences in mapping node relationships \ No newline at end of file diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.zh-CN.md b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.zh-CN.md new file mode 100644 index 000000000..b7b6fd156 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/README.zh-CN.md @@ -0,0 +1,155 @@ +ไธญ | [EN](README.md) + +## Masa.Contrib.BasicAbility.Dcc + +ไฝœ็”จ: + +้€š่ฟ‡Dccๆ‰ฉๅฑ•IConfiguration็ฎก็†่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ›ใ€‚ + +```c# +IConfiguration +โ”œโ”€โ”€ Local ๆœฌๅœฐ่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ +โ”œโ”€โ”€ ConfigurationAPI ่ฟœ็จ‹่Š‚็‚น๏ผˆๅ›บๅฎš Dccๆ‰ฉๅฑ•ๅ…ถ่ƒฝๅŠ›๏ผ‰ +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰่Š‚็‚น +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary ๅญ—ๅ…ธ๏ผˆๅ›บๅฎš๏ผ‰DCCไธญ็ฑปๅž‹ไธบText็š„ๆŒ‚่ฝฝๅˆฐๆญคๅค„ +``` + +็”จไพ‹๏ผš + +```C# +Install-Package Masa.Contrib.Configuration +Install-Package Masa.Contrib.BasicAbility.Dcc //ๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› +``` + +appsettings.json +``` +{ + //Dcc้…็ฝฎ๏ผŒๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ + "DccOptions": { + "ManageServiceAddress ": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "Environment": "Development", + "ConfigObjects": [ "Platforms" ], //ๅพ…ๆŒ‚่ฝฝ็š„ๅฏน่ฑกๅ, ๆญคๅค„ไผšๅฐ†Platforms้…็ฝฎๆŒ‚่ฝฝๅˆฐConfigurationAPI:่Š‚็‚นไธ‹ + "Secret": "", //Dcc App ็ง˜้’ฅ + "Cluster": "Default" +} + +``` + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDccๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› + + options.Mapping(SectionTypes.Local, "Appsettings", ""); //ๅฐ†CustomDccSectionOptionsๆ˜ ๅฐ„ๅˆฐLocalไธ‹็š„Appsettings่Š‚็‚น +}); + +/// +/// ่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป +/// +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; + + [JsonIgnore] + public virtual string? ParentSection { get; init; } = "Replace-With-Your-AppId"; + + [JsonIgnore] + public virtual string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +public class CustomDccSectionOptions +{ + /// + /// The environment name. + /// Get from the environment variable ASPNETCORE_ENVIRONMENT when Environment is null or empty + /// + public string? Environment { get; set; } = null; + + /// + /// The cluster name. + /// + public string? Cluster { get; set; } + + /// + /// The app id. + /// + public string AppId { get; set; } = default!; + + public List ConfigObjects { get; set; } = default!; + + public string? Secret { get; set; } +} +``` + +ๅฆ‚ไฝ•ไฝฟ็”จ้…็ฝฎ๏ผš + +```c# +var app = builder.Build(); + +app.MapGet("/GetPlatform", ([FromServices] IOptions option) => +{ + //ๆŽจ่ + return System.Text.Json.JsonSerializer.Serialize(option.Value); +}); + +app.MapGet("/GetPlatformByMonitor", ([FromServices] IOptionsMonitor options) => +{ + options.OnChange(option => + { + //TODO ้…็ฝฎๆ›ดๆ–ฐ + }); + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +}); + +app.MapGet("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //ๆ ผๅผ๏ผšConfigurationAPI::: + return configuration["ConfigurationAPI::Platforms:Name"]; +}); + +app.Run(); +``` + +ๅฆ‚ไฝ•ๆ›ดๆ–ฐ้…็ฝฎ + + +```c# +var app = builder.Build(); + +app.MapPut("/UpdatePlatform", ([FromServices] IConfigurationAPIManage configurationAPIManage, + [FromServices] IOptions configuration, + PlatformOptions newPlatform) => +{ + //ไฟฎๆ”นDcc้…็ฝฎ + return configurationAPIManage.UpdateAsync(option.Value.Environment, + option.Value.Cluster, + option.Value.AppId, + "" + ,newPlatform); + //ๆญคๅค„Replace-With-Your-ConfigObjectๆ˜ฏPlatforms +}); + +app.Run(); +``` + +ๆ€ป็ป“๏ผš + +DccไธบIConfigurationๆไพ›ไบ†่ฟœ็จ‹้…็ฝฎ็š„็ฎก็†ไปฅๅŠๆŸฅ็œ‹่ƒฝๅŠ›๏ผŒIConfigurationๅฎŒๆ•ด็š„่ƒฝๅŠ›่ฏทๆŸฅ็œ‹[ๆ–‡ๆกฃ](../../Configuration/Masa.Contrib.Configuration/README.zh-CN.md) + +ๆญคๅค„Platformsไธบ่ฟœ็จ‹้…็ฝฎ๏ผŒไป‹็ป็š„ๆ˜ฏ่ฟœ็จ‹้…็ฝฎๆŒ‚่ฝฝๅˆฐIConfigurationไน‹ๅŽ็š„ๆ•ˆๆžœไปฅๅŠ็”จๆณ•๏ผŒๆญค้…็ฝฎไธŽMASA.Contrib.ConfigurationไธญPlatforms็š„ๆฏซๆ— ๅ…ณ็ณป๏ผŒไป…ไป…ๆ˜ฏๅฑ•็คบๅŒไธ€ไธช้…็ฝฎไฟกๆฏๅœจไธคไธชๆบ็š„ไฝฟ็”จๆ–นๅผไปฅๅŠๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป็š„ๅทฎๅˆซ \ No newline at end of file diff --git a/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/_Imports.cs b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/_Imports.cs new file mode 100644 index 000000000..c15218c06 --- /dev/null +++ b/src/BasicAbility/Masa.Contrib.BasicAbility.Dcc/_Imports.cs @@ -0,0 +1,24 @@ +global using Masa.BuildingBlocks.Configuration; +global using Masa.Contrib.BasicAbility.Dcc.Internal; +global using Masa.Contrib.BasicAbility.Dcc.Internal.Model; +global using Masa.Contrib.BasicAbility.Dcc.Internal.Parser; +global using Masa.Contrib.BasicAbility.Dcc.Options; +global using Masa.Utils.Caching.Core.DependencyInjection; +global using Masa.Utils.Caching.Core.Models; +global using Masa.Utils.Caching.DistributedMemory.DependencyInjection; +global using Masa.Utils.Caching.DistributedMemory.Interfaces; +global using Masa.Utils.Caching.Redis.DependencyInjection; +global using Masa.Utils.Caching.Redis.Extensions; +global using Masa.Utils.Caching.Redis.Models; +global using Masa.Utils.Caller.Core; +global using Masa.Utils.Caller.HttpClient; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using System.Collections.Concurrent; +global using System.Diagnostics; +global using System.Dynamic; +global using System.Text.Json; +global using static Masa.Contrib.BasicAbility.Dcc.Internal.Constants; + diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks new file mode 160000 index 000000000..af4058aed --- /dev/null +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -0,0 +1 @@ +Subproject commit af4058aede124ed29f82d57b903527b6e2ee4ab5 diff --git a/src/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs b/src/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs new file mode 100644 index 000000000..59f6fd9a1 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/LocalMasaConfigurationRepository.cs @@ -0,0 +1,71 @@ +namespace Masa.Contrib.Configuration; + +internal class LocalMasaConfigurationRepository : AbstractConfigurationRepository +{ + public override SectionTypes SectionType { get; init; } + + private ConcurrentDictionary _data = new(); + + public LocalMasaConfigurationRepository( + Dictionary sectionRelation, + ILoggerFactory? loggerFactory) + : base(loggerFactory) + { + this.SectionType = SectionTypes.Local; + foreach (var section in sectionRelation) + { + Initialize(section.Key, section.Value); + + ChangeToken.OnChange(() => section.Value.GetReloadToken(), () => + { + Initialize(section.Key, section.Value); + base.FireRepositoryChange(SectionType, Load()); + }); + } + } + + private void Initialize(string rootSectionName, IConfiguration configuration) + { + Dictionary data = new(); + GetData(rootSectionName, configuration, configuration.GetChildren(), ref data); + var properties = new Properties(data); + _data[rootSectionName] = properties; + } + + private void GetData(string rootSectionName, IConfiguration configuration, IEnumerable configurationSections, ref Dictionary dictionary) + { + foreach (var configurationSection in configurationSections) + { + var section = configuration.GetSection(configurationSection.Path); + + var childrenSections = section.GetChildren(); + + if (!section.Exists() || !childrenSections.Any()) + { + var key = string.IsNullOrEmpty(rootSectionName) ? section.Path : $"{rootSectionName}{ConfigurationPath.KeyDelimiter}{section.Path}"; + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, configuration[section.Path]); + } + } + else + { + GetData(rootSectionName, configuration, childrenSections, ref dictionary); + } + } + } + + public override Properties Load() + { + Dictionary localProperties = new(); + foreach (var item in _data) + { + foreach (var key in item.Value.GetPropertyNames()) + { + localProperties[key] = item.Value.GetProperty(key) ?? string.Empty; + } + } + return new Properties(localProperties); + } +} + diff --git a/src/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj b/src/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj new file mode 100644 index 000000000..c406f0464 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/Masa.Contrib.Configuration.csproj @@ -0,0 +1,17 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationBuilder.cs b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationBuilder.cs new file mode 100644 index 000000000..c9d6978c0 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationBuilder.cs @@ -0,0 +1,52 @@ +namespace Masa.Contrib.Configuration; + +public class MasaConfigurationBuilder : IMasaConfigurationBuilder +{ + private readonly IConfigurationBuilder _builder; + + public IDictionary Properties => _builder.Properties; + + public IList Sources => _builder.Sources; + + public Dictionary GetSectionRelations() => SectionRelations; + + internal Dictionary SectionRelations { get; } = new(); + + internal List Repositories { get; } = new(); + + internal List Relations { get; } = new(); + + public MasaConfigurationBuilder(IConfigurationBuilder builder) + => _builder = builder; + + /// + /// + /// + /// + /// If section is null, it is mounted to the Local section + public void AddSection(IConfigurationBuilder configurationBuilder, string? sectionName = null) + { + if (configurationBuilder == null) + throw new ArgumentNullException(nameof(configurationBuilder)); + + if (configurationBuilder.Sources.Count == 0) + throw new ArgumentException("Source cannot be empty"); + + sectionName = sectionName ?? ""; + + if (SectionRelations.ContainsKey(sectionName)) + throw new ArgumentException("Section already exists", nameof(sectionName)); + + SectionRelations.Add(sectionName, configurationBuilder.Build()); + } + + public void AddRepository(IConfigurationRepository configurationRepository) + => Repositories.Add(configurationRepository); + + public void AddRelations(params ConfigurationRelationOptions[] relationOptions) + => Relations.AddRange(relationOptions); + + public IConfigurationBuilder Add(IConfigurationSource source) => _builder.Add(source); + + public IConfigurationRoot Build() => _builder.Build(); +} diff --git a/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationExtensions.cs b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationExtensions.cs new file mode 100644 index 000000000..622410bc5 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationExtensions.cs @@ -0,0 +1,35 @@ +namespace Masa.Contrib.Configuration; + +public static class MasaConfigurationExtensions +{ + public static void UseMasaOptions(this IMasaConfigurationBuilder builder, Action options) + { + var relation = new MasaRelationOptions(); + options.Invoke(relation); + builder.AddRelations(relation.Relations.ToArray()); + } + + internal static void AutoMapping(this MasaConfigurationBuilder builder, params Assembly[] assemblies) + { + var optionTypes = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type != typeof(IMasaConfigurationOptions) && type != typeof(MasaConfigurationOptions) && typeof(IMasaConfigurationOptions).IsAssignableFrom(type)) + .ToList(); + optionTypes.ForEach(optionType => + { + var option = (IMasaConfigurationOptions)Activator.CreateInstance(optionType)!; + var sectionName = option.Section ?? optionType.Name; + if (builder.Relations.Any(relation => relation.SectionType == option.SectionType && relation.Section == sectionName)) + { + throw new ArgumentException("The section has been loaded, no need to load repeatedly, check whether there are duplicate sections or inheritance between auto-mapping classes"); + } + builder.AddRelations(new ConfigurationRelationOptions() + { + SectionType = option.SectionType, + ParentSection = option.ParentSection, + Section = sectionName, + ObjectType = optionType + }); + }); + } +} diff --git a/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationOptions.cs b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationOptions.cs new file mode 100644 index 000000000..2f33b328f --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationOptions.cs @@ -0,0 +1,19 @@ +namespace Masa.Contrib.Configuration; + +public abstract class MasaConfigurationOptions : IMasaConfigurationOptions +{ + /// + /// The name of the parent section, if it is empty, it will be mounted under SectionType, otherwise it will be mounted to the specified section under SectionType + /// + [JsonIgnore] + public virtual string? ParentSection { get; init; } = null; + + /// + /// The section null means same as the class name, else load from the specify section + /// + [JsonIgnore] + public virtual string? Section { get; init; } = null; + + [JsonIgnore] + public abstract SectionTypes SectionType { get; init; } +} diff --git a/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationProvider.cs b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationProvider.cs new file mode 100644 index 000000000..4a63eafac --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationProvider.cs @@ -0,0 +1,64 @@ +namespace Masa.Contrib.Configuration; + +public class MasaConfigurationProvider : ConfigurationProvider, IRepositoryChangeListener, IDisposable +{ + private readonly ConcurrentDictionary _data; + private readonly IEnumerable _configurationRepositories; + + public MasaConfigurationProvider(MasaConfigurationSource source) + { + _data = new(); + _configurationRepositories = source.Builder.Repositories; + + foreach (var configurationRepository in _configurationRepositories) + { + configurationRepository.AddChangeListener(this); + } + } + + public override void Load() + { + foreach (var configurationRepository in _configurationRepositories) + { + var properties = configurationRepository.Load(); + _data[configurationRepository.SectionType] = properties; + } + SetData(); + } + + public void OnRepositoryChange(SectionTypes sectionType, Properties newProperties) + { + if (_data[sectionType] == newProperties) + return; + + _data[sectionType] = newProperties; + + SetData(); + + OnReload(); + } + + void SetData() + { + Dictionary data = new(); + + foreach (var configurationType in _data.Keys) + { + var properties = _data[configurationType]; + foreach (var key in properties.GetPropertyNames()) + { + data[$"{configurationType}{ConfigurationPath.KeyDelimiter}{key}"] = properties.GetProperty(key)!; + } + } + + Data = data; + } + + public void Dispose() + { + foreach (var configurationRepository in _configurationRepositories) + { + configurationRepository.RemoveChangeListener(this); + } + } +} diff --git a/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationSource.cs b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationSource.cs new file mode 100644 index 000000000..8215ca520 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/MasaConfigurationSource.cs @@ -0,0 +1,17 @@ +namespace Masa.Contrib.Configuration; + +public class MasaConfigurationSource : IConfigurationSource +{ + internal MasaConfigurationBuilder Builder; + + public MasaConfigurationSource(MasaConfigurationBuilder builder) + { + Builder = builder; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new MasaConfigurationProvider(this); + } +} + diff --git a/src/Configuration/Masa.Contrib.Configuration/MasaRelationOptions.cs b/src/Configuration/Masa.Contrib.Configuration/MasaRelationOptions.cs new file mode 100644 index 000000000..5a1be9375 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/MasaRelationOptions.cs @@ -0,0 +1,33 @@ +namespace Masa.Contrib.Configuration; + +public class MasaRelationOptions +{ + internal List Relations { get; } = new(); + + /// + /// Map Section relationship + /// + /// + /// + /// parent section, local section is the name of the locally configured section, and ConfigurationAPI is the name of the Appid where the configuration is located + /// The default is null, which is consistent with the mapping class name + /// + /// + public MasaRelationOptions Mapping(SectionTypes sectionType, string parentSection, string? section = null) + { + if (section == null) + section = typeof(TModel).Name; + + if (Relations.Any(relation => relation.SectionType == sectionType && relation.Section == section)) + throw new ArgumentOutOfRangeException(nameof(section), "The current section already has a configuration"); + + Relations.Add(new ConfigurationRelationOptions() + { + SectionType = sectionType, + ParentSection = parentSection, + Section = section, + ObjectType = typeof(TModel) + }); + return this; + } +} diff --git a/src/Configuration/Masa.Contrib.Configuration/README.md b/src/Configuration/Masa.Contrib.Configuration/README.md new file mode 100644 index 000000000..0b439582a --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/README.md @@ -0,0 +1,135 @@ +[ไธญ](README.zh-CN.md) | EN + +## Masa.Contrib.Configuration + +Structure: + +```c# +IConfiguration +โ”œโ”€โ”€ Local Local node (fixed) +โ”‚ โ”œโ”€โ”€ Appsettings Default local node +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ Platforms Custom configuration +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ โ”œโ”€โ”€ Name Parameter name +โ”œโ”€โ”€ ConfigurationAPI Remote node (fixed) +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms Custom node +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name Parameter name +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary Dictionary (fixed) +``` + +Example๏ผš + +```C# +Install-Package Masa.Contrib.Configuration +Install-Package Masa.Contrib.BasicAbility.Dcc //DCC can provide remote configuration capabilities +```json +{ + //Custom configuration + "Platforms": { + "Name": "Masa.Demo" + }, + //Dcc configuration, extended Configuration capabilities, support remote configuration + "DccOptions": { + "ManageServiceAddress": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "ConfigObjects": [ "Platforms" ], //The name of the object to be mounted. Here, the Platforms configuration will be mounted under the ConfigurationAPI: node + "Secret": "", //Dcc App key + "Cluster": "Default" +} +``` + +Automatically map node relationships๏ผš + +```c# +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +//Use MasaConfiguration to take over Configuration, and mount the current Configuration to Local:Appsettings section by default +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//Use Dcc to extend Configuration capabilities and support remote configuration +}); +``` + +Or manually map node relationships๏ผš + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//Use Dcc to extend Configuration capabilities and support remote configuration + + configurationBuilder.UseMasaOptions(options => + { + options.Mapping(SectionTypes.Local, "Appsettings", "Platforms"); //Map the PlatformOptions binding to the Local:Appsettings:Platforms node + }); +}); +``` + +how to use๏ผš + +```c# +var app = builder.Build(); + +app.Map("/GetPlatform", ([FromServices] IOptions option) => +{ + //Recommended (need to automatically or manually map the node relationship before it can be used) + return System.Text.Json.JsonSerializer.Serialize(option.Value); +}); + +app.Map("/GetPlatform", ([FromServices] IOptionsMonitor option) => +{ + //Recommended (need to automatically or manually map the node relationship before it can be used) + options.OnChange(option => + { + //TODO Configuration update service + }); + + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +}); + +app.Map("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //Base + return configuration["Local:Appsettings:Platforms:Name"]; +}); + +app.Run(); +``` + +How to take over more local nodes๏ผŸ + +```c# +builder.AddMasaConfiguration(builder =>{ + + builder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("custom.json", true, true), "Custom");//Mount the custom.json configuration under the Local:Custom node +}); +``` + +Tip๏ผš + +Configuration automatically obtains classes that inherit the IMasaConfigurationOptions interface by default, and maps the node relationship to facilitate obtaining configuration information through IOptions, IOptionsSnapshot, and IOptionsMonitor + +The above Platforms is a local configuration, used to demonstrate the effect and usage of the local configuration after being mounted to IConfiguration \ No newline at end of file diff --git a/src/Configuration/Masa.Contrib.Configuration/README.zh-CN.md b/src/Configuration/Masa.Contrib.Configuration/README.zh-CN.md new file mode 100644 index 000000000..238d6ff08 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/README.zh-CN.md @@ -0,0 +1,140 @@ +ไธญ | [EN](README.md) + +## Masa.Contrib.Configuration + +็ป“ๆž„: + +```c# +IConfiguration +โ”œโ”€โ”€ Local ๆœฌๅœฐ่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ +โ”‚ โ”œโ”€โ”€ Appsettings ้ป˜่ฎคๆœฌๅœฐ่Š‚็‚น +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰้…็ฝฎ +โ”‚ โ”œโ”€โ”€ โ”œโ”€โ”€ โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ +โ”œโ”€โ”€ ConfigurationAPI ่ฟœ็จ‹่Š‚็‚น๏ผˆๅ›บๅฎš๏ผ‰ +โ”‚ โ”œโ”€โ”€ AppId Replace-With-Your-AppId +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms ่‡ชๅฎšไน‰่Š‚็‚น +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ Platforms โ”œโ”€โ”€ Name ๅ‚ๆ•ฐ +โ”‚ โ”œโ”€โ”€ AppId โ”œโ”€โ”€ DataDictionary ๅญ—ๅ…ธ๏ผˆๅ›บๅฎš๏ผ‰ +``` + +็”จไพ‹๏ผš + +```C# +Install-Package Masa.Contrib.Configuration +Install-Package Masa.Contrib.BasicAbility.Dcc //DCCๅฏๆไพ›่ฟœ็จ‹้…็ฝฎ็š„่ƒฝๅŠ› +``` + +appsettings.json +```json +{ + //่‡ชๅฎšไน‰้…็ฝฎ + "Platforms": { + "Name": "Masa.Demo" + }, + //Dcc้…็ฝฎ๏ผŒๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ + "DccOptions": { + "ManageServiceAddress ": "http://localhost:8890", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8889 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "Replace-With-Your-AppId", + "ConfigObjects": [ "Platforms" ], // ่ฆๆŒ‚่ฝฝ็š„ๅฏน่ฑกๅ็งฐ,ๆญคๅค„ไผšๅฐ†Platforms้…็ฝฎๆŒ‚่ฝฝๅˆฐConfigurationAPI:่Š‚็‚นไธ‹ + "Secret": "", //Dcc App ็ง˜้’ฅ + "Cluster": "Default" +} +``` + +่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผš + +```c# +/// +/// ่‡ชๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป +/// +public class PlatformOptions : MasaConfigurationOptions +{ + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = "Platforms"; + + public string Name { get; set; } +} + +//ไฝฟ็”จMasaConfigurationๆŽฅ็ฎกConfiguration๏ผŒ้ป˜่ฎคไผšๅฐ†ๅฝ“ๅ‰็š„ConfigurationๆŒ‚่ฝฝๅˆฐLocal:Appsettings่Š‚็‚น +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDcc ๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ +}); +``` + +ๆˆ–ๆ‰‹ๅŠจๆทปๅŠ ๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผš + +```C# +builder.AddMasaConfiguration(configurationBuilder => +{ + //configurationBuilder.UseDcc(builder.Services);//ไฝฟ็”จDcc ๆ‰ฉๅฑ•Configuration่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ฟœ็จ‹้…็ฝฎ + + configurationBuilder.UseMasaOptions(options => + { + options.Mapping(SectionTypes.Local, "Appsettings", "Platforms"); //ๅฐ†PlatformOptions็ป‘ๅฎšๆ˜ ๅฐ„ๅˆฐLocal:Appsettings:Platforms่Š‚็‚น + }); +}); +``` + +ๅฆ‚ไฝ•ไฝฟ็”จ๏ผš + +```c# +var app = builder.Build(); + +app.Map("/GetPlatform", ([FromServices] IOptions option) => +{ + //ๆŽจ่๏ผˆ้œ€่ฆ่‡ชๅŠจๆˆ–ๆ‰‹ๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณปๅŽๆ‰่ƒฝไฝฟ็”จ๏ผ‰ + return System.Text.Json.JsonSerializer.Serialize(option.Value); +}); + +app.Map("/GetPlatform", ([FromServices] IOptionsMonitor option) => +{ + options.OnChange(option => + { + //TODO ้…็ฝฎๆ›ดๆ–ฐไธšๅŠก + }); + + return System.Text.Json.JsonSerializer.Serialize(option.CurrentValue); +});//ๆŽจ่๏ผˆ้œ€่ฆ่‡ชๅŠจๆˆ–ๆ‰‹ๅŠจๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณปๅŽๆ‰่ƒฝไฝฟ็”จ๏ผ‰ + +app.Map("/GetPlatformName", ([FromServices] IConfiguration configuration) => +{ + //ๅŸบ็ก€ + return configuration["Local:Appsettings:Platforms:Name"]; +}); + +app.Run(); +``` + +ๅฆ‚ไฝ•ๆŽฅ็ฎกๆ›ดๅคš็š„ๆœฌๅœฐ่Š‚็‚น๏ผŸ + +```c# +builder.AddMasaConfiguration(builder =>{ + + builder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("custom.json", true, true), "Custom");//ๅฐ†custom.json้…็ฝฎๆŒ‚่ฝฝๅˆฐLocal:Custom่Š‚็‚นไธ‹ +}); +``` + +ๆ็คบ๏ผš + +Configuration้ป˜่ฎค่‡ชๅŠจ่Žทๅ–็ปงๆ‰ฟIMasaConfigurationOptionsๆŽฅๅฃ็š„็ฑป๏ผŒๅนถๆ˜ ๅฐ„่Š‚็‚นๅ…ณ็ณป๏ผŒๆ–นไพฟ้€š่ฟ‡IOptionsใ€IOptionsSnapshotใ€IOptionsMonitor่Žทๅ–้…็ฝฎไฟกๆฏ + +ไธŠๆ–‡Platformsไธบๆœฌๅœฐ้…็ฝฎ๏ผŒ็”จไบŽๆผ”็คบๆœฌๅœฐ้…็ฝฎๆŒ‚่ฝฝๅˆฐIConfigurationๅŽ็š„ๆ•ˆๆžœไปฅๅŠไฝฟ็”จ็”จๆณ• \ No newline at end of file diff --git a/src/Configuration/Masa.Contrib.Configuration/ServiceCollectionExtensions.cs b/src/Configuration/Masa.Contrib.Configuration/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..0b09e7e16 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/ServiceCollectionExtensions.cs @@ -0,0 +1,138 @@ +namespace Masa.Contrib.Configuration; + +public static class ServiceCollectionExtensions +{ + public static WebApplicationBuilder AddMasaConfiguration( + this WebApplicationBuilder builder, + Action? configureDelegate = null) + => builder.AddMasaConfiguration(configureDelegate, + "Appsettings", + AppDomain.CurrentDomain.GetAssemblies()); + + public static WebApplicationBuilder AddMasaConfiguration( + this WebApplicationBuilder builder, + Action? configureDelegate, + string defaultSectionName = "Appsettings", + params Assembly[] assemblies) + { + var configurationBuilder = GetConfigurationBuilder(builder.Configuration); + + IConfigurationRoot masaConfiguration = builder.Services.CreateMasaConfiguration(configureDelegate, configurationBuilder, defaultSectionName, assemblies); + if (!masaConfiguration.Providers.Any()) + return builder; + + Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.ConfigureAppConfiguration(builder.Host, configBuilder => + { + configBuilder.Sources.Clear(); + }); + builder.Configuration.AddConfiguration(masaConfiguration); + + return builder; + } + + public static IConfigurationRoot CreateMasaConfiguration( + this IServiceCollection services, + Action? configureDelegate, + IConfigurationBuilder? configurationBuilder = null, + string defaultSectionName = "Appsettings", + params Assembly[] assemblies) + { + if (services.Any(service => service.ImplementationType == typeof(MasaConfigurationProvider))) + return new ConfigurationBuilder().Build(); + + services.AddSingleton(); + services.AddOptions(); + + MasaConfigurationBuilder masaConfigurationBuilder = new MasaConfigurationBuilder(new ConfigurationBuilder()); + if (configurationBuilder != null) + { + masaConfigurationBuilder.AddSection(configurationBuilder, defaultSectionName); + } + configureDelegate?.Invoke(masaConfigurationBuilder); + + if (masaConfigurationBuilder.SectionRelations.Count == 0) + throw new Exception("Please add the section to be loaded"); + + var localConfigurationRepository = new LocalMasaConfigurationRepository(masaConfigurationBuilder.SectionRelations, services.BuildServiceProvider().GetService()); + masaConfigurationBuilder.AddRepository(localConfigurationRepository); + + var source = new MasaConfigurationSource(masaConfigurationBuilder); + var configuration = masaConfigurationBuilder.Add(source).Build(); + + masaConfigurationBuilder.AutoMapping(assemblies); + masaConfigurationBuilder.Relations.ForEach(relation => + { + List sectionNames = new List() + { + relation.SectionType.ToString(), + }; + if (!string.IsNullOrEmpty(relation.ParentSection)) + sectionNames.Add(relation.ParentSection); + + if (relation.Section != "") + { + sectionNames.AddRange(relation.Section.Split(ConfigurationPath.KeyDelimiter)); + } + + services.ConfigureOption(configuration, sectionNames, relation.ObjectType); + }); + + return configuration; + } + + private static IConfigurationBuilder GetConfigurationBuilder(ConfigurationManager configuration) + { + var configurationBuilder = new ConfigurationBuilder(); + foreach (var source in ((IConfigurationBuilder)configuration).Sources) + { + configurationBuilder.Add(source); + } + return configurationBuilder; + } + + private static void ClearSource(this WebApplicationBuilder builder) + { + Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.ConfigureAppConfiguration(builder.Host, configBuilder => + { + configBuilder.Sources.Clear(); + }); + } + + internal static void ConfigureOption( + this IServiceCollection services, + IConfiguration configuration, + List sectionNames, Type optionType) + { + IConfigurationSection? configurationSection = null; + foreach (var sectionName in sectionNames) + { + if (configurationSection == null) + configurationSection = configuration.GetSection(sectionName); + else + configurationSection = configurationSection.GetSection(sectionName); + } + if (!configurationSection.Exists()) + { + throw new ArgumentNullException("Section", "Check if the mapping section is correct"); + } + + var configurationChangeTokenSource = + Activator.CreateInstance(typeof(ConfigurationChangeTokenSource<>).MakeGenericType(optionType), string.Empty, + configurationSection)!; + services.TryAdd(new ServiceDescriptor(typeof(IOptionsChangeTokenSource<>).MakeGenericType(optionType), + configurationChangeTokenSource)); + + Action configureBinder = _ => { }; + var configureOptions = + Activator.CreateInstance(typeof(NamedConfigureFromConfigurationOptions<>).MakeGenericType(optionType), + string.Empty, + configurationSection, configureBinder)!; + services.TryAdd(new ServiceDescriptor(typeof(IConfigureOptions<>).MakeGenericType(optionType), + configureOptions)); + } + + private class MasaConfigurationProvider + { + + } +} diff --git a/src/Configuration/Masa.Contrib.Configuration/_Imports.cs b/src/Configuration/Masa.Contrib.Configuration/_Imports.cs new file mode 100644 index 000000000..5d9ff0d02 --- /dev/null +++ b/src/Configuration/Masa.Contrib.Configuration/_Imports.cs @@ -0,0 +1,15 @@ +global using Masa.BuildingBlocks.Configuration; +global using Masa.BuildingBlocks.Configuration.Options; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using Microsoft.Extensions.Primitives; +global using System; +global using System.Collections.Concurrent; +global using System.Collections.Generic; +global using System.Linq; +global using System.Reflection; +global using System.Text.Json.Serialization; diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/Masa.Contrib.Data.Contracts.EF.csproj b/src/Data/Masa.Contrib.Data.Contracts.EF/Masa.Contrib.Data.Contracts.EF.csproj new file mode 100644 index 000000000..feb674d58 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/Masa.Contrib.Data.Contracts.EF.csproj @@ -0,0 +1,19 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/README.md b/src/Data/Masa.Contrib.Data.Contracts.EF/README.md new file mode 100644 index 000000000..44769b40c --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/README.md @@ -0,0 +1,31 @@ +[ไธญ](README.zh-CN.md) | EN + +## Contracts.EF + +Example๏ผš + +```C# +Install-Package Masa.Contrib.Data.UoW.EF +Install-Package Masa.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + dbOptions.UseSoftDelete(builder.Services); + }); +}); +``` + +> When the entity inherits ISoftware and is deleted, change the delete state to the modified state, and cooperate with the custom Remove operation to achieve soft deletion +> Do not query the data marked as soft deleted when querying + +> Frequently Asked Questions: + +- Problem 1: After using UseSoftDelete, there is a problem that the submission cannot be saved + + After using Uow, the transaction will be enabled by default after Addใ€ Modifiedใ€ and Deleted + and the transaction can be saved normally after the transaction is submitted + If the EventBus is used, the transaction will be automatically submitted \ No newline at end of file diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/README.zh-CN.md b/src/Data/Masa.Contrib.Data.Contracts.EF/README.zh-CN.md new file mode 100644 index 000000000..066b692bf --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/README.zh-CN.md @@ -0,0 +1,31 @@ +ไธญ | [EN](README.md) + +## Contracts.EF + +็”จไพ‹๏ผš + +```C# +Install-Package Masa.Contrib.Data.UoW.EF +Install-Package Masa.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + dbOptions.UseSoftDelete(builder.Services);//ๅฏๅŠจ่ฝฏๅˆ ้™ค + }); +}); +``` + +> ๅฝ“ๅฎžไฝ“็ปงๆ‰ฟISoftware๏ผŒไธ”่ขซๅˆ ้™คๆ—ถ๏ผŒๅฐ†ๅˆ ้™ค็Šถๆ€ๆ”นไธบไฟฎๆ”น็Šถๆ€๏ผŒๅนถ้…ๅˆ่‡ชๅฎšไน‰Removeๆ“ไฝœ๏ผŒๅฎž็Žฐ่ฝฏๅˆ ้™ค +> ๆ”ฏๆŒๆŸฅ่ฏข็š„ๆ—ถๅ€™ไธๆŸฅ่ฏข่ขซๆ ‡่ฎฐ่ฝฏๅˆ ้™ค็š„ๆ•ฐๆฎ + +> ๅธธ่ง้—ฎ้ข˜๏ผš + +- ้—ฎ้ข˜1๏ผšไฝฟ็”จUseSoftDeleteๅŽๅ‡บ็Žฐๆไบคไฟๅญ˜ไธไธŠ็š„้—ฎ้ข˜ + + ไฝฟ็”จUowๅŽ๏ผŒ้ป˜่ฎคๅœจ่ฟ›่กŒAddใ€Modifiedใ€DeletedๅŽไผšๅฏ็”จไบ‹ๅŠก + ้œ€่ฆๆไบคไบ‹ๅŠกไน‹ๅŽๆ‰่ƒฝๆญฃๅธธไฟๅญ˜ + ๅฆ‚ๆžœไฝฟ็”จEventBusๅˆ™ไผš่‡ชๅŠจๆไบคไบ‹ๅŠก \ No newline at end of file diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..83efacd89 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/ServiceCollectionExtensions.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Data.Contracts.EF; + +public static class ServiceCollectionExtensions +{ + public static MasaDbContextOptionsBuilder UseSoftDelete( + this MasaDbContextOptionsBuilder optionsBuilder, IServiceCollection services) + { + if (services.Any(s => s.ImplementationType == typeof(ContractsFilter))) return optionsBuilder; + services.AddSingleton(); + + if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) + throw new Exception("Please add UoW first."); + + optionsBuilder.UseQueryFilterProvider() + .UseSaveChangesFilter(); + + return optionsBuilder; + } + + private class ContractsFilter + { + } +} diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs new file mode 100644 index 000000000..4abd18aae --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/QueryFilterProvider.cs @@ -0,0 +1,19 @@ +namespace Masa.Contrib.Data.Contracts.EF.SoftDelete; + +public class QueryFilterProvider : IQueryFilterProvider +{ + public LambdaExpression OnExecuting(IMutableEntityType entityType) + { + var parameter = Expression.Parameter(entityType.ClrType); + if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType)) + { + var propertyMethodInfo = typeof(Microsoft.EntityFrameworkCore.EF).GetMethod("Property")!.MakeGenericMethod(typeof(bool)); + var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant(nameof(ISoftDelete.IsDeleted))); + var compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false)); + var lambda = Expression.Lambda(compareExpression, parameter); + return lambda; + } + + return default!; + } +} diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs new file mode 100644 index 000000000..5fded1a40 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/SoftDelete/SoftDeleteSaveChangesFilter.cs @@ -0,0 +1,17 @@ +namespace Masa.Contrib.Data.Contracts.EF.SoftDelete; + +public class SoftDeleteSaveChangesFilter : ISaveChangesFilter +{ + public void OnExecuting(ChangeTracker changeTracker) + { + changeTracker.DetectChanges(); + foreach (var entity in changeTracker.Entries().Where(e => e.State == Microsoft.EntityFrameworkCore.EntityState.Deleted)) + { + if (entity.Entity is ISoftDelete) + { + entity.State = Microsoft.EntityFrameworkCore.EntityState.Modified; + entity.CurrentValues[nameof(ISoftDelete.IsDeleted)] = true; + } + } + } +} diff --git a/src/Data/Masa.Contrib.Data.Contracts.EF/_Imports.cs b/src/Data/Masa.Contrib.Data.Contracts.EF/_Imports.cs new file mode 100644 index 000000000..b4a2cd361 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.Contracts.EF/_Imports.cs @@ -0,0 +1,9 @@ +global using Masa.BuildingBlocks.Data.Contracts; +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.Contrib.Data.Contracts.EF.SoftDelete; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Microsoft.EntityFrameworkCore.Metadata; +global using Microsoft.Extensions.DependencyInjection; +global using System.Linq.Expressions; diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..909dd1f59 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -0,0 +1,43 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public static class DispatcherOptionsExtensions +{ + public static IDispatcherOptions UseUoW( + this IDispatcherOptions options, + Action? optionsBuilder = null, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + { + if (options.Services == null) + throw new ArgumentNullException(nameof(options.Services)); + + if (options.Services.Any(service => service.ImplementationType == typeof(UoWProvider))) + return options; + + options.Services.AddSingleton(); + + options.Services.AddScoped(serviceProvider => + { + var dbContext = serviceProvider.GetRequiredService(); + var logger = serviceProvider.GetService>>(); + return new UnitOfWork(dbContext, logger) + { + DisableRollbackOnFailure = disableRollbackOnFailure, + UseTransaction = useTransaction + }; + }); + if (options.Services.All(service => service.ServiceType != typeof(MasaDbContextOptions))) + options.Services.AddMasaDbContext(optionsBuilder); + + options.Services.AddScoped(); + + return options; + } + + private class UoWProvider + { + + } +} + diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/Masa.Contrib.Data.UoW.EF.csproj b/src/Data/Masa.Contrib.Data.UoW.EF/Masa.Contrib.Data.UoW.EF.csproj new file mode 100644 index 000000000..effd0ec90 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/Masa.Contrib.Data.UoW.EF.csproj @@ -0,0 +1,20 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/README.md b/src/Data/Masa.Contrib.Data.UoW.EF/README.md new file mode 100644 index 000000000..2b437b70f --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/README.md @@ -0,0 +1,20 @@ +[ไธญ](README.zh-CN.md) | EN + +## Contracts.EF + +Example๏ผš + +```C# +Install-Package Masa.Contrib.Data.UoW.EF +Install-Package Masa.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + }); +}); +``` + diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md b/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md new file mode 100644 index 000000000..7990ecf74 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md @@ -0,0 +1,20 @@ +ไธญ | [EN](README.md) + +## Contracts.EF + +็”จไพ‹๏ผš + +```C# +Install-Package Masa.Contrib.Data.UoW.EF +Install-Package Masa.Contrib.Data.Contracts.EF +``` + +```C# +builder.Services.AddEventBus(options => { + options.UseUoW(dbOptions => + { + dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"); + }); +}); +``` + diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/Transaction.cs b/src/Data/Masa.Contrib.Data.UoW.EF/Transaction.cs new file mode 100644 index 000000000..f0e9fea5b --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/Transaction.cs @@ -0,0 +1,9 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public class Transaction : ITransaction +{ + public Transaction(IUnitOfWork unitOfWork) => UnitOfWork = unitOfWork; + + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } +} diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs new file mode 100644 index 000000000..e9f2575ad --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs @@ -0,0 +1,66 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public class UnitOfWork : IAsyncDisposable, IUnitOfWork + where TDbContext : MasaDbContext +{ + public DbTransaction Transaction + { + get + { + if (!UseTransaction) + throw new NotSupportedException("Doesn't support transaction opening"); + + if (TransactionHasBegun) + return _context.Database.CurrentTransaction!.GetDbTransaction(); + + return _context.Database.BeginTransaction().GetDbTransaction(); + } + } + + public bool TransactionHasBegun => _context.Database.CurrentTransaction != null; + + public bool DisableRollbackOnFailure { get; set; } + + public EntityState EntityState { get; set; } + + public CommitState CommitState { get; set; } + + public bool UseTransaction { get; set; } = true; + + private readonly DbContext _context; + + private readonly ILogger>? _logger; + + public UnitOfWork(TDbContext dbContext, ILogger>? logger = null) + { + _context = dbContext; + _logger = logger; + } + + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + await _context.SaveChangesAsync(cancellationToken); + EntityState = EntityState.Unchanged; + } + + public async Task CommitAsync(CancellationToken cancellationToken = default) + { + if (!UseTransaction || !TransactionHasBegun) + throw new NotSupportedException("Transaction not opened"); + + await _context.Database.CommitTransactionAsync(cancellationToken); + CommitState = CommitState.Commited; + } + + public async Task RollbackAsync(CancellationToken cancellationToken = default) + { + if (!UseTransaction || !TransactionHasBegun) + throw new NotSupportedException("Transactions are not opened and rollback is not supported"); + + await _context.Database.RollbackTransactionAsync(cancellationToken); + } + + public ValueTask DisposeAsync() => _context.DisposeAsync(); + + public void Dispose() => _context.Dispose(); +} diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs b/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs new file mode 100644 index 000000000..cad5525d3 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs @@ -0,0 +1,11 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Storage; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using System.Data.Common; +global using System.Text.Json.Serialization; +global using EntityState = Masa.BuildingBlocks.Data.UoW.EntityState; + diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/DispatcherOptionsExtensions.cs b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..bd4bbb6bc --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/DispatcherOptionsExtensions.cs @@ -0,0 +1,34 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF; + +public static class DispatcherOptionsExtensions +{ + public static IDispatcherOptions UseRepository( + this IDispatcherOptions options) + where TDbContext : DbContext + => options.UseRepository(AppDomain.CurrentDomain.GetAssemblies()); + + public static IDispatcherOptions UseRepository( + this IDispatcherOptions options, + params Assembly[] assemblies) + where TDbContext : DbContext + { + if (options.Services == null) + throw new ArgumentNullException(nameof(options.Services)); + + if (options.Services.Any(service => service.ImplementationType == typeof(RepositoryProvider))) + return options; + + options.Services.AddSingleton(); + + if (options.Services.All(service => service.ServiceType != typeof(IUnitOfWork))) + throw new Exception("Please add UoW first."); + + options.Services.TryAddRepository(assemblies); + return options; + } + + private class RepositoryProvider + { + + } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/LinqExtensions.cs b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/LinqExtensions.cs new file mode 100644 index 000000000..bccf42675 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/LinqExtensions.cs @@ -0,0 +1,93 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Internal; + +internal static class LinqExtensions +{ + public static IQueryable GetQueryable(this IQueryable query, Dictionary fields) where TEntity : class + { + foreach (var field in fields) + { + query = query.GetQueryable(field.Key, field.Value); + } + return query; + } + + private static IQueryable GetQueryable(this IQueryable query, string field, object val) where TEntity : class + { + Type type = typeof(TEntity); + var parameter = Expression.Parameter(type, "entity"); + + PropertyInfo property = type.GetProperty(field)!; + Expression expProperty = Expression.Property(parameter, property.Name); + + Expression> valueLamda = () => val; + Expression expValue = Expression.Convert(valueLamda.Body, property.PropertyType); + Expression expression = Expression.Equal(expProperty, expValue); + Expression> filter = (Expression>)Expression.Lambda(expression, parameter); + return query.Where(filter); + } + + public static IQueryable OrderBy(this IQueryable query, Dictionary fields) where TEntity : class + { + var index = 0; + foreach (var field in fields) + { + if (index == 0) + query = query.OrderBy(field.Key, field.Value); + else + query = query.ThenBy(field.Key, field.Value); + index++; + } + + return query; + } + + private static IQueryable OrderBy(this IQueryable query, string field, bool desc) where TEntity : class + { + ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity)); + Expression key = Expression.Property(parameterExpression, field); + var propertyInfo = GetPropertyInfo(typeof(TEntity), field); + var orderExpression = GetOrderExpression(typeof(TEntity), propertyInfo); + if (desc) + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(TEntity), propertyInfo.PropertyType); + return (genericMethod.Invoke(null, new object[] { query, orderExpression }) as IQueryable)!; + } + else + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(TEntity), propertyInfo.PropertyType); + return (IQueryable)genericMethod.Invoke(null, new object[] { query, orderExpression })!; + } + } + + private static IQueryable ThenBy(this IQueryable query, string field, bool desc) where T : class + { + ParameterExpression parameterExpression = Expression.Parameter(typeof(T)); + Expression key = Expression.Property(parameterExpression, field); + var propertyInfo = GetPropertyInfo(typeof(T), field); + var orderExpression = GetOrderExpression(typeof(T), propertyInfo); + if (desc) + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "ThenByDescending" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType); + return (genericMethod.Invoke(null, new object[] { query, orderExpression }) as IQueryable)!; + } + else + { + var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "ThenBy" && m.GetParameters().Length == 2); + var genericMethod = method!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType); + return (IQueryable)genericMethod.Invoke(null, new object[] { query, orderExpression })!; + } + } + + private static PropertyInfo GetPropertyInfo(Type entityType, string field) + => entityType.GetProperties().FirstOrDefault(p => p.Name.Equals(field, StringComparison.OrdinalIgnoreCase))!; + + private static LambdaExpression GetOrderExpression(Type entityType, PropertyInfo propertyInfo) + { + var parametersExpression = Expression.Parameter(entityType); + var fieldExpression = Expression.PropertyOrField(parametersExpression, propertyInfo.Name); + return Expression.Lambda(fieldExpression, parametersExpression); + } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs new file mode 100644 index 000000000..e5d9c74c4 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Internal/ServiceCollectionRepositoryExtensions.cs @@ -0,0 +1,105 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Internal; + +internal static class ServiceCollectionRepositoryExtensions +{ + /// + /// The relationship between entity and keys + /// + public static Dictionary Relations = new(); + + public static IServiceCollection TryAddRepository( + this IServiceCollection services, + params Assembly[] assemblies) + where TDbContext : DbContext + { + if (assemblies == null || assemblies.Length == 0) + { + throw new ArgumentNullException(nameof(assemblies)); + } + + var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()); + var entityTypes = allTypes.Where(type => type.IsAggregateRootEntity()); + foreach (var entityType in entityTypes) + { + var repositoryInterfaceType = typeof(IRepository<>).MakeGenericType(entityType); + + services.TryAddAddDefaultRepository(repositoryInterfaceType, GetRepositoryImplementationType(typeof(TDbContext), entityType)); + + services.TryAddCustomRepository(repositoryInterfaceType, allTypes.ToArray()); + + var keys = GetKeys(entityType); + CheckKeys(entityType, keys); + Relations.TryAdd(entityType, keys); + } + + return services; + } + + private static string[] GetKeys(Type entityType) + { + IAggregateRoot aggregateRoot; + try + { + var constructorInfo = entityType + .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .FirstOrDefault(con => !con.CustomAttributes.Any()); + + if (constructorInfo == null) + throw new ArgumentNullException("The entity needs to have an empty constructor"); + + aggregateRoot = (IAggregateRoot)Activator.CreateInstance(entityType, constructorInfo.IsPrivate)!; + } + catch (Exception) + { + throw new ArgumentNullException("The entity needs to have an empty constructor"); + } + + var keys = aggregateRoot.GetKeys().Select(k => k.Name).ToArray(); + if (keys.Length != keys.Where(key => !string.IsNullOrEmpty(key)).Distinct().Count()) + throw new ArgumentException("The joint primary key cannot be empty"); + + return keys; + } + + /// + /// Check if the combined primary key is correct + /// + private static void CheckKeys(Type entityType, string[] fields) + { + foreach (var field in fields) + { + if (!entityType.GetProperties().Any(p => p.Name.Equals(field, StringComparison.OrdinalIgnoreCase))!) + throw new ArgumentException("Check if the combined primary key is correct"); + } + } + + private static bool IsAggregateRootEntity(this Type type) + => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && type != typeof(Entity) && typeof(IAggregateRoot).IsAssignableFrom(type); + + private static IServiceCollection TryAddCustomRepository(this IServiceCollection services, Type repositoryInterfaceType, Type[] allTypes) + { + var customerRepositoryInterfaceTypes = allTypes.Where(type => type.GetInterfaces().Any(t => t == repositoryInterfaceType) && type.IsInterface && !type.IsGenericType); + foreach (var customerRepositoryInterfaceType in customerRepositoryInterfaceTypes) + { + var customerRepositoryImplementationTypes = allTypes.Where(type => type.IsClass && customerRepositoryInterfaceType.IsAssignableFrom(type)).ToList(); + if (customerRepositoryImplementationTypes.Count != 1) + { + throw new NotSupportedException($"The number of types of {customerRepositoryInterfaceType.FullName} implementation classes must be 1"); + } + services.TryAddScoped(customerRepositoryInterfaceType, customerRepositoryImplementationTypes.FirstOrDefault()!); + } + return services; + } + + private static IServiceCollection TryAddAddDefaultRepository(this IServiceCollection services, Type repositoryInterfaceType, Type repositoryImplementationType) + { + if (repositoryInterfaceType.IsAssignableFrom(repositoryImplementationType)) + { + services.TryAddScoped(repositoryInterfaceType, repositoryImplementationType); + } + return services; + } + + public static Type GetRepositoryImplementationType(Type dbContextType, Type entityType) + => typeof(Repository<,>).MakeGenericType(dbContextType, entityType); +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Masa.Contrib.Ddd.Domain.Repository.EF.csproj b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Masa.Contrib.Ddd.Domain.Repository.EF.csproj new file mode 100644 index 000000000..d544c5a83 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Masa.Contrib.Ddd.Domain.Repository.EF.csproj @@ -0,0 +1,18 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.md b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.md new file mode 100644 index 000000000..9755c6b38 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.md @@ -0,0 +1,71 @@ +[ไธญ](README.zh-CN.md) | EN + +## Masa.Contrib.Ddd.Domain.Repository.EF + +Example๏ผš + +```c# +Install-Package Masa.Contrib.Ddd.Domain.Repository.EF +``` + +> Advantages: The EF version of IRepository provides basic CRUD + +1. Add Repository.EF + +```c# +builder.Services +.AddDomainEventBus(options => +{ + options.UseRepository();//Use the EF version of Repository to achieve +} +``` + +2. How to use + +```C# +public class ProductItem +{ + public string Name { get; set; } +} + +public class DemoService : ServiceBase +{ + public CatalogService(IServiceCollection services) : base(services) + { + + } + + public async Task CreateProduct(ProductItem product,[FromService]IRepository repository) + { + await repository.AddAsync(product); + await repository.UnitOfWork.SaveChangesAsync(); + } +} +``` + +If the method defined by IRepository is not enough to support the business, you can customize the Repository + +```C# +public interface IProductRepository : IRepository +{ + Task> ItemsWithNameAsync(string name); +} + +public class ProductRepository : Repository, IProductRepository +{ + public Task> ItemsWithNameAsync(string name) + { + //Todo + } +} +``` + +In use๏ผš + +```C# +public async Task> ItemsWithNameAsync(string name, [FromService] IProductRepository productRepository) +{ + return await productRepository.ItemsWithNameAsync(name); +} +``` + diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.zh-CN.md b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.zh-CN.md new file mode 100644 index 000000000..4770e9e1c --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/README.zh-CN.md @@ -0,0 +1,71 @@ +ไธญ | [EN](README.md) + +## Masa.Contrib.Ddd.Domain.Repository.EF + +็”จไพ‹๏ผš + +```c# +Install-Package Masa.Contrib.Ddd.Domain.Repository.EF +``` + +> ไผ˜ๅŠฟ๏ผšIRepository็š„EF็‰ˆๅฎž็Žฐ๏ผŒๆไพ›ไบ†ๅŸบ็ก€็š„CRUD + +1. ๆทปๅŠ Repository.EF + +```c# +builder.Services +.AddDomainEventBus(options => +{ + options.UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ +} +``` + +2. ๅฆ‚ไฝ•ไฝฟ็”จ + +```C# +public class ProductItem +{ + public string Name { get; set; } +} + +public class DemoService : ServiceBase +{ + public CatalogService(IServiceCollection services) : base(services) + { + + } + + public async Task CreateProduct(ProductItem product,[FromService]IRepository repository) + { + await repository.AddAsync(product); + await repository.UnitOfWork.SaveChangesAsync(); + } +} +``` + +ๅฆ‚ๆžœIRepositoryๅฎšไน‰็š„ๆ–นๆณ•ไธ่ถณไปฅๆ”ฏๆ’‘ไธšๅŠก๏ผŒๅˆ™ๅฏไปฅ่‡ชๅฎšไน‰Repository + +```C# +public interface IProductRepository : IRepository +{ + Task> ItemsWithNameAsync(string name); +} + +public class ProductRepository : Repository, IProductRepository +{ + public Task> ItemsWithNameAsync(string name) + { + //Todo + } +} +``` + +ๅœจไฝฟ็”จไธŠ๏ผš + +```C# +public async Task> ItemsWithNameAsync(string name, [FromService] IProductRepository productRepository) +{ + return await productRepository.ItemsWithNameAsync(name); +} +``` + diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs new file mode 100644 index 000000000..156166a25 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs @@ -0,0 +1,213 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF; + +public class Repository : BaseRepository + where TEntity : class, IAggregateRoot + where TDbContext : DbContext +{ + protected readonly TDbContext _context; + + public Repository(TDbContext context, IUnitOfWork unitOfWork) + { + _context = context; + UnitOfWork = unitOfWork; + } + + public override bool TransactionHasBegun + => _context.Database.CurrentTransaction != null; + + public override DbTransaction Transaction + { + get + { + if (!UseTransaction) + throw new NotSupportedException(nameof(Transaction)); + + if (TransactionHasBegun) + return _context.Database.CurrentTransaction!.GetDbTransaction(); + + return _context.Database.BeginTransaction().GetDbTransaction(); + } + } + + public override bool UseTransaction + { + get => UnitOfWork.UseTransaction; + set => UnitOfWork.UseTransaction = value; + } + + public override IUnitOfWork UnitOfWork { get; } + + public override EntityState EntityState + { + get => UnitOfWork.EntityState; + set + { + UnitOfWork.EntityState = value; + if (value == EntityState.Changed) + CheckAndOpenTransaction(); + } + } + + public override async ValueTask AddAsync( + TEntity entity, + CancellationToken cancellationToken = default) + { + var response = (await _context.AddAsync(entity, cancellationToken).AsTask()).Entity; + EntityState = EntityState.Changed; + return response; + } + + public override async Task AddRangeAsync( + IEnumerable entities, + CancellationToken cancellationToken = default) + { + await _context.AddRangeAsync(entities, cancellationToken); + EntityState = EntityState.Changed; + } + + public override Task CommitAsync(CancellationToken cancellationToken = default) + => UnitOfWork.CommitAsync(cancellationToken); + + public override async ValueTask DisposeAsync() => await _context.DisposeAsync(); + + public override void Dispose() => _context.Dispose(); + + public override Task FindAsync( + object?[]? keyValues, + CancellationToken cancellationToken = default) + { + if (keyValues == null) + return Task.FromResult(default(TEntity?)); + + var keys = GetKeys(typeof(TEntity)); + Dictionary fields = new(); + for (var i = 0; i < keys.Length; i++) + { + fields.Add(keys[i], keyValues[i]!); + } + + return _context.Set().IgnoreQueryFilters().GetQueryable(fields).FirstOrDefaultAsync(cancellationToken); + } + + public override Task FindAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + => _context.Set().Where(predicate).FirstOrDefaultAsync(cancellationToken); + + public override async Task GetCountAsync(CancellationToken cancellationToken = default) + => await _context.Set().LongCountAsync(cancellationToken); + + public override Task GetCountAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + => _context.Set().LongCountAsync(predicate, cancellationToken); + + public override async Task> GetListAsync(CancellationToken cancellationToken = default) + => await _context.Set().ToListAsync(cancellationToken); + + public override async Task> GetListAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + => await _context.Set().Where(predicate).ToListAsync(cancellationToken); + + /// + /// + /// + /// + /// + /// asc or desc, default asc + /// + /// + public override Task> GetPaginatedListAsync( + int skip, + int take, + Dictionary? sorting, + CancellationToken cancellationToken = default) + { + sorting ??= new Dictionary(GetKeys(typeof(TEntity)).Select(key => new KeyValuePair(key, false))); + + return _context.Set().OrderBy(sorting).Skip(skip).Take(take).ToListAsync(cancellationToken); + } + + /// + /// + /// + /// condition + /// + /// + /// asc or desc, default asc + /// + /// + public override Task> GetPaginatedListAsync( + Expression> predicate, + int skip, + int take, + Dictionary? sorting, + CancellationToken cancellationToken = default) + { + sorting ??= new Dictionary(GetKeys(typeof(TEntity)).Select(key => new KeyValuePair(key, false))); + + return _context.Set().Where(predicate).OrderBy(sorting).Skip(skip).Take(take).ToListAsync(cancellationToken); + } + + public override Task RemoveAsync(TEntity entity, CancellationToken cancellationToken = default) + { + _context.Set().Remove(entity); + EntityState = EntityState.Changed; + return Task.FromResult(entity); + } + + public override async Task RemoveAsync(Expression> predicate, CancellationToken cancellationToken = default) + { + var entities = await GetListAsync(predicate, cancellationToken); + EntityState = EntityState.Changed; + _context.Set().RemoveRange(entities); + } + + public override Task RemoveRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) + { + _context.Set().RemoveRange(entities); + EntityState = EntityState.Changed; + return Task.CompletedTask; + } + + public override Task RollbackAsync(CancellationToken cancellationToken = default) + => UnitOfWork.RollbackAsync(cancellationToken); + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + await UnitOfWork.SaveChangesAsync(cancellationToken); + } + + public override Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) + { + _context.Set().Update(entity); + EntityState = EntityState.Changed; + return Task.FromResult(entity); + } + + public override Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) + { + _context.Set().UpdateRange(entities); + EntityState = EntityState.Changed; + return Task.CompletedTask; + } + + /// + /// When additions, deletions and changes are made through the Repository and the transaction is currently allowed and the transaction is not opened, the transaction is started + /// + private void CheckAndOpenTransaction() + { + if (!UnitOfWork.UseTransaction) + return; + + if (!UnitOfWork.TransactionHasBegun) + { + _ = UnitOfWork.Transaction; // Open the transaction + } + CommitState = CommitState.UnCommited; + } + + protected string[] GetKeys(Type entityType) + => ServiceCollectionRepositoryExtensions.Relations[entityType]!; +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/_Imports.cs b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/_Imports.cs new file mode 100644 index 000000000..3caf98b1c --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/_Imports.cs @@ -0,0 +1,15 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Ddd.Domain.Entities; +global using Masa.BuildingBlocks.Ddd.Domain.Repositories; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Contrib.Ddd.Domain.Repository.EF.Internal; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Storage; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using System.Collections.Concurrent; +global using System.Data.Common; +global using System.Linq.Expressions; +global using System.Reflection; +global using EntityState = Masa.BuildingBlocks.Data.UoW.EntityState; diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/DomainEventBus.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/DomainEventBus.cs new file mode 100644 index 000000000..e734bd45a --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/DomainEventBus.cs @@ -0,0 +1,84 @@ +namespace Masa.Contrib.Ddd.Domain; + +public class DomainEventBus : IDomainEventBus +{ + protected readonly IEventBus _eventBus; + protected readonly IIntegrationEventBus _integrationEventBus; + private readonly IUnitOfWork _unitOfWork; + private readonly DispatcherOptions _options; + + private readonly ConcurrentQueue> _eventQueue = new(); + + public DomainEventBus( + IEventBus eventBus, + IIntegrationEventBus integrationEventBus, + IUnitOfWork unitOfWork, + IOptions options) + { + _eventBus = eventBus; + _integrationEventBus = integrationEventBus; + _unitOfWork = unitOfWork; + _options = options.Value; + } + + public async Task PublishAsync(TEvent @event) where TEvent : IEvent + { + if (@event is IDomainEvent domainEvent && !IsAssignableFromDomainQuery(@event.GetType())) + { + domainEvent.UnitOfWork = _unitOfWork; + } + if (@event is IIntegrationEvent integrationEvent) + { + await _integrationEventBus.PublishAsync((TEvent)integrationEvent); + } + else + { + await _eventBus.PublishAsync(@event); + } + + bool IsAssignableFromDomainQuery(Type? type) + { + if (type == null) + return false; + + if (!type.IsGenericType) + { + return IsAssignableFromDomainQuery(type.BaseType); + } + return type.GetInterfaces().Any(type => type.GetGenericTypeDefinition() == typeof(IDomainQuery<>)); + } + } + + public Task Enqueue(TDomentEvent @event) where TDomentEvent : IDomainEvent + { + _eventQueue.Enqueue(new KeyValuePair(@event.GetType(), @event)); + return Task.CompletedTask; + } + + public async Task PublishQueueAsync() + { + while (_eventQueue.TryDequeue(out KeyValuePair @event)) + { + await PublishAsync(@event.Key, @event.Value); + } + } + + private async Task PublishAsync(Type type, TEvent @event) where TEvent : IEvent + { + if (@event is IIntegrationEvent integrationEvent) + { + await PublishAsync(integrationEvent); + } + else + { + var parameters = Convert.ChangeType(@event, type); + var invokeDelegate = InvokeBuilder.Build(_eventBus.GetType(), nameof(_eventBus.PublishAsync), type); + await invokeDelegate.Invoke(_eventBus, parameters); + } + } + + public async Task CommitAsync(CancellationToken cancellationToken = default) + => await _unitOfWork.CommitAsync(cancellationToken); + + public IEnumerable GetAllEventTypes() => _options.AllEventTypes.Concat(_eventBus.GetAllEventTypes()).Distinct(); +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/DomainService.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/DomainService.cs new file mode 100644 index 000000000..56a7ecf48 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/DomainService.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Ddd.Domain; + +public class DomainService : IDomainService +{ + public IDomainEventBus EventBus { get; } + + public DomainService(IDomainEventBus eventBus) => EventBus = eventBus; +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainCommand.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainCommand.cs new file mode 100644 index 000000000..4a64d466a --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainCommand.cs @@ -0,0 +1,15 @@ +namespace Masa.Contrib.Ddd.Domain.Events; + +public record DomainCommand(Guid Id, DateTime CreationTime) : IDomainCommand +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } + + public DomainCommand() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainEvent.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainEvent.cs new file mode 100644 index 000000000..9cda98249 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainEvent.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Ddd.Domain.Events; + +public record DomainEvent(Guid Id, DateTime CreationTime) : IDomainEvent +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } + + public DomainEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } + +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainQuery.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainQuery.cs new file mode 100644 index 000000000..7c0997a21 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/DomainQuery.cs @@ -0,0 +1,22 @@ +namespace Masa.Contrib.Ddd.Domain.Events; + +public abstract record DomainQuery(Guid Id, DateTime CreationTime) : IDomainQuery + where TResult : notnull +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + [JsonIgnore] + public IUnitOfWork? UnitOfWork + { + get => null; + set => throw new NotSupportedException(nameof(UnitOfWork)); + } + + public abstract TResult Result { get; set; } + + public DomainQuery() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Events/IntegrationDomainEvent.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/IntegrationDomainEvent.cs new file mode 100644 index 000000000..cb1de138c --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Events/IntegrationDomainEvent.cs @@ -0,0 +1,9 @@ +namespace Masa.Contrib.Ddd.Domain.Events; + +public abstract record IntegrationDomainEvent(Guid Id, DateTime CreationTime) : DomainEvent(Id, CreationTime), IIntegrationDomainEvent +{ + [JsonIgnore] + public abstract string Topic { get; set; } + + public IntegrationDomainEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Internal/InvokeBuilder.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/Internal/InvokeBuilder.cs new file mode 100644 index 000000000..cd2b56389 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Internal/InvokeBuilder.cs @@ -0,0 +1,21 @@ +๏ปฟnamespace Masa.Contrib.Ddd.Domain.Internal; + +internal class InvokeBuilder +{ + internal delegate Task TaskInvokeDelegate(object target, params object[] parameters); + + internal static TaskInvokeDelegate Build(Type targetType, string methodName, Type parameterType) + { + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(0)); + var valueCast = Expression.Convert(valueObj, parameterType); + var instanceCast = Expression.Convert(targetParameter, targetType); + var methodCall = Expression.Call(instanceCast, methodName, new[] { parameterType }, valueCast); + + var castMethodCall = Expression.Convert(methodCall, typeof(Task)); + var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); + return lambda.Compile(); + } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj b/src/Ddd/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj new file mode 100644 index 000000000..30414a42b --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Masa.Contrib.Ddd.Domain.csproj @@ -0,0 +1,18 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/Options/DispatcherOptions.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/Options/DispatcherOptions.cs new file mode 100644 index 000000000..549f233ea --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/Options/DispatcherOptions.cs @@ -0,0 +1,40 @@ +namespace Masa.Contrib.Ddd.Domain; + +public class DispatcherOptions : IDispatcherOptions +{ + private Assembly[] _assemblies = new Assembly[0]; + + public Assembly[] Assemblies + { + get => _assemblies; + set + { + _assemblies = value; + if (_assemblies == null || _assemblies.Length == 0) + { + throw new ArgumentNullException(nameof(_assemblies)); + } + Types = _assemblies.SelectMany(assembly => assembly.GetTypes()); + AllEventTypes = GetTypes(typeof(IEvent)).ToList(); + AllDomainServiceTypes = GetTypes(typeof(DomainService)).ToList(); + AllAggregateRootTypes = GetTypes(typeof(IAggregateRoot)).Where(type => IsAggregateRootEntity(type)).ToList(); + } + } + + private bool IsAggregateRootEntity(Type type) + => type.IsClass && !type.IsGenericType && !type.IsAbstract && type != typeof(AggregateRoot) && typeof(IAggregateRoot).IsAssignableFrom(type); + + private IEnumerable Types { get; set; } + + private IEnumerable GetTypes(Type type) => Types.Where(t => t.IsClass && type.IsAssignableFrom(t)); + + internal List AllEventTypes { get; private set; } + + internal List AllDomainServiceTypes { get; private set; } + + internal List AllAggregateRootTypes { get; private set; } + + public IServiceCollection Services { get; } + + public DispatcherOptions(IServiceCollection services) => Services = services; +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/README.md b/src/Ddd/Masa.Contrib.Ddd.Domain/README.md new file mode 100644 index 000000000..9981bf0ed --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/README.md @@ -0,0 +1,119 @@ +[ไธญ](README.zh-CN.md) | EN + +### DomainEventBus + +Example๏ผš + +```c# +Install-Package Masa.Contrib.Ddd.Domain +Install-Package Masa.Contrib.Ddd.Domain.Repository.EF + +Install-Package Masa.Contrib.Dispatcher.Events + +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.Dapr +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF +Install-Package Masa.Contrib.Data.UoW.EF +``` + +1. Add DomainEventBus + +```C# +builder.Services +.AddDomainEventBus(options => +{ + options.UseEventBus()//Use in-process events + .UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) + .UseDaprEventBus()///Use cross-process events + .UseEventLog() + .UseRepository();//Use the EF version of Repository to achieve +}) +``` + +2. Add DomainCommand + +```C# +public class RegisterUserDomainCommand : DomainCommand +{ + public string User { get; set; } = default!; + + public string Password { get; set; } = default!; + + public string PhoneNumber { get; set; } = default!; +} +``` +> DomainQuery refers to Query in Cqrs + +3. Add Handler (in process) + +```C# +public class UserHandler +{ + [EventHandler] + public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) + { + //TODO Registered user business + } +} +``` + +4. Send DomainCommand + +```C# +IDomainEventBus eventBus;//Get IDomainEventBus through DI +await eventBus.PublishAsync(new RegisterUserDomainCommand());//Send DomainCommand +``` + +5. Define domain events + +```C# +public class RegisterUserSucceededIntegrationEvent : IntegrationDomainEvent +{ + public override string Topic { get; set; } = nameof(RegisterUserSucceededIntegrationEvent); + + public string Account { get; set; } = default!; +} + +public class RegisterUserSucceededEvent : DomainEvent +{ + public string Account { get; set; } = default!; +} +``` + +6. Define domain services + +```C# +public class UserDomainService : DomainService +{ + public UserDomainService(IDomainEventBus eventBus) : base(eventBus) + { + } + + public async Task RegisterSucceededAsync(string account) + { + await EventBus.Enqueue(new RegisterUserSucceededIntegrationEvent() { Account = account }); + await EventBus.Enqueue(new RegisterUserSucceededEvent() { Account = account }); + await EventBus.PublishQueueAsync(); + } +} +``` + +> DomainEvent (in-process) and IntegrationDomainEvent (cross-process) can be inherited as needed +> +> If you only need to send a domain event, you can directly use EventBus.PublishQueueAsync(new RegisterUserSucceededEvent()) +> +> If you want to send in a unified way, you can send it through EventBus.Enqueue() and finally call EventBus.PublishQueueAsync() + +Tip๏ผš + +> 1. The use of DomainEventBus must require the implementation of IEventBus and IIntegrationEventBus and IUnitOfWork +> 2. EventBus only supports in-process scheduling, cross-process scheduling is not supported, and the sending order is consistent with the enqueue order, but the actual execution order is unknown + +7. Cross-process event subscription + +```C# +[Topic("pubsub", nameof(RegisterUserSucceededIntegrationEvent))] +public async Task RegisterUserSucceededHandlerAsync(RegisterUserSucceededIntegrationEvent @event) +{ + //todo +} +``` \ No newline at end of file diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/README.zh-CN.md b/src/Ddd/Masa.Contrib.Ddd.Domain/README.zh-CN.md new file mode 100644 index 000000000..9f739df30 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/README.zh-CN.md @@ -0,0 +1,119 @@ +ไธญ | [EN](README.md) + +### DomainEventBus + +็”จไพ‹๏ผš + +```c# +Install-Package Masa.Contrib.Ddd.Domain +Install-Package Masa.Contrib.Ddd.Domain.Repository.EF + +Install-Package Masa.Contrib.Dispatcher.Events + +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.Dapr +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF +Install-Package Masa.Contrib.Data.UoW.EF +``` + +1. ๆทปๅŠ DomainEventBus + +```C# +builder.Services +.AddDomainEventBus(options => +{ + options.UseEventBus()//ไฝฟ็”จ่ฟ›็จ‹ๅ†…ไบ‹ไปถ + .UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=idientity")) + .UseDaprEventBus()///ไฝฟ็”จ่ทจ่ฟ›็จ‹ไบ‹ไปถ + .UseEventLog() + .UseRepository();//ไฝฟ็”จRepository็š„EF็‰ˆๅฎž็Žฐ +}) +``` + +2. ๆทปๅŠ DomainCommand + +```C# +public class RegisterUserDomainCommand : DomainCommand +{ + public string User { get; set; } = default!; + + public string Password { get; set; } = default!; + + public string PhoneNumber { get; set; } = default!; +} +``` +> DomainQueryๅ‚่€ƒCqrsไธญ็š„Query + +3. ๆทปๅŠ Handler๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ + +```C# +public class UserHandler +{ + [EventHandler] + public Task RegisterUserHandlerAsync(RegisterUserDomainCommand command) + { + //TODO ๆณจๅ†Œ็”จๆˆทไธšๅŠก + } +} +``` + +4. ๅ‘้€DomainCommand + +```C# +IDomainEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIDomainEventBus +await eventBus.PublishAsync(new RegisterUserDomainCommand());//ๅ‘้€DomainCommand +``` + +5. ๅฎšไน‰้ข†ๅŸŸไบ‹ไปถ + +```C# +public class RegisterUserSucceededIntegrationEvent : IntegrationDomainEvent +{ + public override string Topic { get; set; } = nameof(RegisterUserSucceededIntegrationEvent); + + public string Account { get; set; } = default!; +} + +public class RegisterUserSucceededEvent : DomainEvent +{ + public string Account { get; set; } = default!; +} +``` + +6. ๅฎšไน‰้ข†ๅŸŸๆœๅŠก + +```C# +public class UserDomainService : DomainService +{ + public UserDomainService(IDomainEventBus eventBus) : base(eventBus) + { + } + + public async Task RegisterSucceededAsync(string account) + { + await EventBus.Enqueue(new RegisterUserSucceededIntegrationEvent() { Account = account }); + await EventBus.Enqueue(new RegisterUserSucceededEvent() { Account = account }); + await EventBus.PublishQueueAsync(); + } +} +``` + +> ๅฏๆ นๆฎ้œ€่ฆ็ปงๆ‰ฟDomainEvent๏ผˆ่ฟ›็จ‹ๅ†…๏ผ‰ใ€IntegrationDomainEvent๏ผˆ่ทจ่ฟ›็จ‹๏ผ‰ +> +> ๅฆ‚ๆžœๅช้œ€่ฆๅ‘้€ไธ€ไธช้ข†ๅŸŸไบ‹ไปถ๏ผŒๅˆ™ๅฏไปฅ็›ดๆŽฅไฝฟ็”จEventBus.PublishQueueAsync(new RegisterUserSucceededEvent())ๅณๅฏ +> +> ๅฆ‚ๆžœๅธŒๆœ›็ปŸไธ€ๅ‘้€๏ผŒๅˆ™ๅฏไปฅ้€š่ฟ‡EventBus.Enqueue()ใ€ๆœ€ๅŽ่ฐƒ็”จEventBus.PublishQueueAsync()ๅ‘้€ + +ๆ็คบ๏ผš + +> 1. ไฝฟ็”จDomainEventBusๅฟ…้กป่ฆๆฑ‚ๅฎž็ŽฐIEventBusไปฅๅŠIIntegrationEventBusไปฅๅŠIUnitOfWork +> 2. EventBusๅชๆ”ฏๆŒ่ฟ›็จ‹ๅ†…็ผ–ๆŽ’ใ€่ทจ่ฟ›็จ‹ไธๆ”ฏๆŒ็ผ–ๆŽ’๏ผŒๅ‘้€้กบๅบไธŽๅ…ฅ้˜Ÿ้กบๅบไธ€่‡ด๏ผŒไฝ†ๅฎž้™…ๆ‰ง่กŒ้กบๅบๆœช็Ÿฅ + +7. ่ทจ่ฟ›็จ‹ไบ‹ไปถ่ฎข้˜… + +```C# +[Topic("pubsub", nameof(RegisterUserSucceededIntegrationEvent))] +public async Task RegisterUserSucceededHandlerAsync(RegisterUserSucceededIntegrationEvent @event) +{ + //todo +} +``` diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/ServiceCollectionExtensions.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..6e6e18758 --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/ServiceCollectionExtensions.cs @@ -0,0 +1,67 @@ +namespace Masa.Contrib.Ddd.Domain; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddDomainEventBus( + this IServiceCollection services, + Action? options = null) + { + if (services.Any(service => service.ImplementationType == typeof(DomainEventBusProvider))) + return services; + + services.AddSingleton(); + + var dispatcherOptions = new DispatcherOptions(services); + options?.Invoke(dispatcherOptions); + if (dispatcherOptions.Assemblies.Length == 0) + { + dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + } + services.AddSingleton(typeof(IOptions), serviceProvider => Options.Create(dispatcherOptions)); + + if (services.All(service => service.ServiceType != typeof(IEventBus))) + { + throw new Exception("Please add EventBus first."); + } + + if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) + { + throw new Exception("Please add UoW first."); + } + + if (services.All(service => service.ServiceType != typeof(IIntegrationEventBus))) + { + throw new Exception("Please add IntegrationEventBus first."); + } + + services.CheckAggregateRootRepositories(dispatcherOptions.AllAggregateRootTypes); + + foreach (var domainServiceType in dispatcherOptions.AllDomainServiceTypes) + { + services.TryAddScoped(domainServiceType); + } + + services.TryAddScoped(); + services.TryAddScoped(); + return services; + } + + private static void CheckAggregateRootRepositories(this IServiceCollection services, List aggregateRootRepositoryTypes) + { + foreach (var aggregateRootRepositoryType in aggregateRootRepositoryTypes) + { + var serviceType = GetServiceRepositoryType(aggregateRootRepositoryType); + if (services.All(service => service.ServiceType != serviceType)) + { + throw new NotImplementedException($"The number of types of {serviceType.FullName} implementation class must be 1."); + } + } + } + + private static Type GetServiceRepositoryType(Type entityType) => typeof(IRepository<>).MakeGenericType(entityType); + + private class DomainEventBusProvider + { + + } +} diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain/_Imports.cs b/src/Ddd/Masa.Contrib.Ddd.Domain/_Imports.cs new file mode 100644 index 000000000..a312bb7bf --- /dev/null +++ b/src/Ddd/Masa.Contrib.Ddd.Domain/_Imports.cs @@ -0,0 +1,15 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Ddd.Domain.Entities; +global using Masa.BuildingBlocks.Ddd.Domain.Events; +global using Masa.BuildingBlocks.Ddd.Domain.Repositories; +global using Masa.BuildingBlocks.Ddd.Domain.Services; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.Contrib.Ddd.Domain.Internal; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Options; +global using System.Collections.Concurrent; +global using System.Linq.Expressions; +global using System.Reflection; +global using System.Text.Json.Serialization; diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..3186447b9 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/DispatcherOptionsExtensions.cs @@ -0,0 +1,26 @@ +namespace Masa.Contrib.Dispatcher.Events; + +public static class DispatcherOptionsExtensions +{ + public static IDispatcherOptions UseEventBus(this IDispatcherOptions options) + => options.UseEventBus(AppDomain.CurrentDomain.GetAssemblies()); + + public static IDispatcherOptions UseEventBus( + this IDispatcherOptions options, + params Assembly[] assemblies) + => options.UseEventBus(ServiceLifetime.Scoped, assemblies); + + public static IDispatcherOptions UseEventBus( + this IDispatcherOptions options, + ServiceLifetime lifetime, + params Assembly[] assemblies) + { + if (options.Services == null) + { + throw new ArgumentNullException(nameof(options.Services)); + } + + options.Services.AddEventBus(lifetime, options => options.Assemblies = assemblies); + return options; + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Enums/FailureLevels.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Enums/FailureLevels.cs new file mode 100644 index 000000000..846fc47f3 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Enums/FailureLevels.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Dispatcher.Events.Enums; + +public enum FailureLevels +{ + Throw = 1, + + ThrowAndCancel, + + Ignore +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Event.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Event.cs new file mode 100644 index 000000000..1dbc7837c --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Event.cs @@ -0,0 +1,12 @@ +namespace Masa.Contrib.Dispatcher.Events; + +public record Event(Guid Id, DateTime CreationTime) : IEvent +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + public Event() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventBus.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventBus.cs new file mode 100644 index 000000000..15d1677d5 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventBus.cs @@ -0,0 +1,67 @@ +namespace Masa.Contrib.Dispatcher.Events; + +public class EventBus : IEventBus +{ + private readonly IServiceProvider _serviceProvider; + + private readonly Internal.Dispatch.Dispatcher _dispatcher; + + private readonly DispatcherOptions _options; + + private IUnitOfWork? _unitOfWork; + + private readonly string LoadEventHelpLink = "https://github.com/masastack/Masa.Contrib/tree/develop/docs/LoadEvent.md"; + + public EventBus(IServiceProvider serviceProvider, IOptions options) + { + _serviceProvider = serviceProvider; + _dispatcher = serviceProvider.GetRequiredService(); + _options = options.Value; + } + + public async Task PublishAsync(TEvent @event) where TEvent : IEvent + { + var eventType = typeof(TEvent); + if (@event is null) + { + throw new ArgumentNullException(eventType.Name); + } + + var middlewares = _serviceProvider.GetRequiredService>>(); + if (!_options.UnitOfWorkRelation.ContainsKey(eventType)) + { + throw new NotSupportedException($"Getting \"{eventType.Name}\" relationship chain failed, see {LoadEventHelpLink} for details. "); + } + + if (_options.UnitOfWorkRelation[eventType]) + { + ITransaction transactionEvent = (ITransaction) @event; + var unitOfWork = _serviceProvider.GetService(); + if (unitOfWork != null) + { + transactionEvent.UnitOfWork = unitOfWork; + if (_unitOfWork is null) + { + _unitOfWork = transactionEvent.UnitOfWork; + } + else + { + middlewares = middlewares.Where(middleware => middleware is not TransactionMiddleware); + } + } + } + + EventHandlerDelegate publishEvent = async () => { await _dispatcher.PublishEventAsync(_serviceProvider, @event); }; + await middlewares.Reverse().Aggregate(publishEvent, (next, middleware) => () => middleware.HandleAsync(@event, next))(); + } + + public IEnumerable GetAllEventTypes() => _options.AllEventTypes; + + public async Task CommitAsync(CancellationToken cancellationToken = default) + { + if (_unitOfWork is null) + throw new ArgumentNullException("You need to UseUoW when adding services"); + + await _unitOfWork.CommitAsync(cancellationToken); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventHandlerAttribute.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventHandlerAttribute.cs new file mode 100644 index 000000000..4671678ab --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/EventHandlerAttribute.cs @@ -0,0 +1,150 @@ +namespace Masa.Contrib.Dispatcher.Events; + +[AttributeUsage(AttributeTargets.Method)] +public class EventHandlerAttribute : Attribute +{ + public EventHandlerAttribute() : this(DefaultOrder) + { + } + + public EventHandlerAttribute(int order) : this(order, false) + { + + } + + public EventHandlerAttribute(int order, bool enableRetry) : this(order, enableRetry, enableRetry ? DefaultRetryCount : 0) + { + + } + + public EventHandlerAttribute(int order, FailureLevels failureLevels) : this(order, failureLevels, false) + { + + } + + public EventHandlerAttribute(int order, bool enableRetry, int retryTimes) : this(order, FailureLevels.Throw, enableRetry, retryTimes) + { + + } + + public EventHandlerAttribute(int order, bool enableRetry, bool isCancel) : this(order, enableRetry, isCancel, enableRetry ? DefaultRetryCount : 0) + { + + } + + public EventHandlerAttribute(int order, bool enableRetry, bool isCancel, int retryTimes) : this(order, FailureLevels.Throw, enableRetry, retryTimes, isCancel) + { + + } + + public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry) : this(order, failureLevels, enableRetry, enableRetry ? DefaultRetryCount : 0) + { + + } + + public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry, bool isCancel) : this(order, failureLevels, enableRetry, enableRetry ? DefaultRetryCount : 0, isCancel) + { + + } + + public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry, int retryTimes) : this(order, failureLevels, enableRetry, retryTimes, false) + { + + } + + public EventHandlerAttribute(int order, FailureLevels failureLevels, bool enableRetry, int retryTimes, bool isCancel) + { + Order = order; + FailureLevels = failureLevels; + EnableRetry = enableRetry; + RetryTimes = enableRetry ? retryTimes : 0; + IsCancel = isCancel; + } + + /// + /// The default number of retry attempts. + /// + private const int DefaultRetryCount = 3; + + private const int DefaultOrder = int.MaxValue; + + /// + /// Used to control the order in which methods are executed, in ascending order. default is 100 + /// Must be greater than or equal to 0 + /// + public int Order { get; set; } + + public FailureLevels FailureLevels { get; set; } + + public bool EnableRetry { get; set; } + + /// + /// The default value is 3๏ผŒEnableRetry must be true to take effect + /// + public int RetryTimes { get; set; } + + /// + /// Used to cancel or compensate + /// + public bool IsCancel { get; set; } + + internal MethodInfo ActionMethodInfo { get; set; } + + internal Type InstanceType { get; set; } + + internal Type EventType { get; set; } + + internal int ActualRetryTimes => EnableRetry ? RetryTimes : 0; + + internal TaskInvokeDelegate InvokeDelegate { get; private set; } + + private object Instance { get; set; } = default!; + + private object EventHandler { get; set; } + + internal bool IsEventHandler => FailureLevels == FailureLevels.Throw || FailureLevels == FailureLevels.ThrowAndCancel; + + internal void BuildExpression() + { + InvokeDelegate = InvokeBuilder.Build(ActionMethodInfo, InstanceType); + } + + internal async Task ExcuteAction(IServiceProvider serviceProvider, TEvent @event) where TEvent : IEvent + { + if (InvokeDelegate != null) + { + Instance = serviceProvider.GetRequiredService(InstanceType); + await InvokeDelegate.Invoke(Instance, @event); + } + else + { + await ExcuteSagaAction(serviceProvider, @event); + } + } + + private async Task ExcuteSagaAction(IServiceProvider serviceProvider, TEvent @event) where TEvent : IEvent + { + if (!IsCancel) + { + if (EventHandler == null) + { + var handlers = serviceProvider.GetServices>(); + var handler = handlers.Where(x => x.GetType().Equals(InstanceType)).FirstOrDefault()!; + EventHandler = handler; + } + await ((IEventHandler)EventHandler).HandleAsync(@event); + } + else + { + if (EventHandler == null) + { + var handlers = serviceProvider.GetServices>(); + var handler = handlers.Where(x => x.GetType().Equals(InstanceType)).FirstOrDefault()!; + EventHandler = handler; + } + await ((ISagaEventHandler)EventHandler).CancelAsync(@event); + } + } + + internal bool IsHandlerMissing(int maxCancelOrder) => FailureLevels == FailureLevels.ThrowAndCancel && Order < maxCancelOrder || FailureLevels == FailureLevels.Throw && Order <= maxCancelOrder; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs new file mode 100644 index 000000000..af5f95b28 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatchRelationNetwork.cs @@ -0,0 +1,99 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal.Dispatch; + +internal class DispatchRelationNetwork +{ + public Dictionary> RelationNetwork { get; set; } = new(); + + public Dictionary> HandlerRelationNetwork { get; set; } = new(); + + public Dictionary> CancelRelationNetwork { get; set; } = new(); + + private readonly ILogger? _logger; + + public DispatchRelationNetwork(ILogger? logger) => _logger = logger; + + public void Add(Type keyEventType, EventHandlerAttribute handler) + { + Add(keyEventType, handler, !handler.IsCancel ? HandlerRelationNetwork : CancelRelationNetwork); + } + + /// + /// If the relationship does not exist in the network, add it + /// + /// + /// + /// + private void Add(Type keyEventType, + EventHandlerAttribute handlers, + Dictionary> dispatchRelativeNetwork) + { + if (!dispatchRelativeNetwork.ContainsKey(keyEventType)) + { + dispatchRelativeNetwork.Add(keyEventType, new List()); + } + + if (!dispatchRelativeNetwork[keyEventType].Any(x => x.ActionMethodInfo.Equals(handlers.ActionMethodInfo) && x.InstanceType.Equals(handlers.InstanceType))) + { + dispatchRelativeNetwork[keyEventType].Add(handlers); + } + } + + public void Build() + { + Sort(); + CheckConstraints(); + RelationNetwork = HandlerRelationNetwork.ToDictionary(relationNetwork => relationNetwork.Key, + relationNetwork => relationNetwork.Value.Select(handler => new DispatchRelationOptions(handler)).ToList()); + + foreach (var relation in RelationNetwork) + { + foreach (var relationOption in RelationNetwork[relation.Key]!) + { + if (CancelRelationNetwork.TryGetValue(relation.Key, out List? cancelRelations)) + { + var cancelHandlers = cancelRelations.TakeWhile(handler => relationOption.IsCancelHandler(relationOption.Handler)).Reverse().ToList(); + relationOption.AddCancelHandler(cancelHandlers); + } + } + } + } + + private void Sort() + { + HandlerRelationNetwork = Sort(HandlerRelationNetwork); + CancelRelationNetwork = Sort(CancelRelationNetwork); + } + + private Dictionary> Sort(Dictionary> dispatchRelatives) + where TDispatchHandlerAttribute : EventHandlerAttribute + { + return dispatchRelatives.ToDictionary( + dispatchRelative => dispatchRelative.Key, + dispatchRelative => dispatchRelative.Value.OrderBy(attr => attr.Order).ToList() + ); + } + + /// + /// Checking scheduling Relationships + /// Throw an exception for a Handler that only has Cancel + /// and warn a Handler that the Cancel will never perform because the Order is improperly set + /// + private void CheckConstraints() + { + foreach (var cancelRelation in CancelRelationNetwork) + { + if (HandlerRelationNetwork.All(relation => relation.Key != cancelRelation.Key)) + { + throw new NotSupportedException($"{cancelRelation.Key.Name} is only have a cancel handler, it must have an event handler."); + } + + var maxCancelOrder = cancelRelation.Value.Max(handler => handler.Order); + var maxHandlerOrder = HandlerRelationNetwork[cancelRelation.Key].Where(handler => handler.IsEventHandler).OrderByDescending(handler => handler.Order).ThenByDescending(handler => handler.FailureLevels).FirstOrDefault(); + if (maxHandlerOrder == null || maxHandlerOrder.IsHandlerMissing(maxCancelOrder)) + { + var methodName = cancelRelation.Value.Select(x => x.ActionMethodInfo.Name).LastOrDefault(); + _logger?.LogWarning($"The {methodName} method is meaningless, because its Order attribute is too large, and no handler corresponding to the Order can be triggered. It is suggested to lower the Order attribute of {methodName} or add a matching handler - {cancelRelation.Value.Select(x => x.InstanceType.FullName).LastOrDefault()}"); + } + } + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs new file mode 100644 index 000000000..dd27cf022 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/Dispatcher.cs @@ -0,0 +1,68 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal.Dispatch; + +internal class Dispatcher : DispatcherBase +{ + public Dispatcher(IServiceCollection services, Assembly[] assemblies, bool forceInit = false) : base(services, assemblies, forceInit) { } + + public Dispatcher Build(ServiceLifetime lifetime) + { + foreach (var assembly in _assemblies) + { + AddRelationNetwork(assembly); + } + foreach (var dispatchInstance in GetAddServiceTypeList()) + { + _services.Add(dispatchInstance, dispatchInstance, lifetime); + } + Build(); + return this; + } + + private void AddRelationNetwork(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) + { + if (!type.IsConcrete()) + { + continue;//Handler and Cancel must be normal classes, not abstract classes or interfaces + } + + foreach (var method in type.GetMethods()) + { + AddRelationNetwork(type, method); + } + } + } + + private void AddRelationNetwork(Type type, MethodInfo method) + { + var attribute = method.GetCustomAttributes(typeof(EventHandlerAttribute), true).FirstOrDefault(); + var handler = attribute as EventHandlerAttribute; + if (attribute is not null && handler is not null) + { + var parameters = method.GetParameters(); + if (parameters == null || + parameters.Length != 1 || + !parameters.Any(parameter => typeof(IEvent).IsAssignableFrom(parameter.ParameterType))) + { + throw new ArgumentOutOfRangeException(string.Format("[{0}] must have only one argument and inherit from Event", method.Name)); + } + if (IsSagaMode(type, method)) + { + return; + } + + if (handler.Order < 0) + { + throw new ArgumentOutOfRangeException("The order must be greater than or equal to 0"); + } + + var parameter = parameters.FirstOrDefault()!; + handler.ActionMethodInfo = method; + handler.InstanceType = type; + handler.EventType = parameter.ParameterType; + handler.BuildExpression(); + AddRelationNetwork(parameter.ParameterType, handler); + } + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs new file mode 100644 index 000000000..9176c2e20 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/DispatcherBase.cs @@ -0,0 +1,128 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal.Dispatch; + +internal class DispatcherBase +{ + protected static DispatchRelationNetwork? _sharingRelationNetwork; + + protected readonly IServiceCollection _services; + + protected readonly Assembly[] _assemblies; + + private readonly ILogger? _logger; + + public DispatcherBase(IServiceCollection services, Assembly[] assemblies, bool forceInit) + { + _services = services; + _assemblies = assemblies; + var serviceProvider = services.BuildServiceProvider(); + if (_sharingRelationNetwork == null || forceInit) + { + _sharingRelationNetwork = new DispatchRelationNetwork(serviceProvider.GetService>()); + } + _logger = serviceProvider.GetService>(); + } + + public async Task PublishEventAsync(IServiceProvider serviceProvider, TEvent @event) + where TEvent : IEvent + { + var eventType = typeof(TEvent); + if (!_sharingRelationNetwork!.RelationNetwork.TryGetValue(eventType, out List? dispatchRelations)) + { + if (@event is IIntegrationEvent) + { + _logger?.LogError($"Dispatcher: The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); + throw new ArgumentNullException($"The current event is an out-of-process event. You should use IIntegrationEventBus or IDomainEventBus to send it"); + } + + _logger?.LogError($"Dispatcher: The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); + throw new ArgumentNullException($"The {eventType.FullName} Handler method was not found. Check to see if the EventHandler feature is added to the method and if the Assembly is specified when using EventBus"); + } + await ExecuteEventHandlerAsync(serviceProvider, dispatchRelations, @event); + } + + private async Task ExecuteEventHandlerAsync(IServiceProvider serviceProvider, + List dispatchRelations, + TEvent @event) + where TEvent : IEvent + { + var executionStrategy = serviceProvider.GetRequiredService(); + StrategyOptions strategyOptions = new StrategyOptions(); + bool isCancel = false; + EventHandlerAttribute dispatchHandler; + foreach (var dispatchRelation in dispatchRelations) + { + if (isCancel) return; + dispatchHandler = dispatchRelation.Handler; + + strategyOptions.SetStrategy(dispatchHandler); + + await executionStrategy.ExecuteAsync(strategyOptions, @event, async (@event) => + { + _logger?.LogDebug("----- Publishing event {@Event}: message id: {messageId} -----", @event, @event.Id); + await dispatchHandler.ExcuteAction(serviceProvider, @event); + }, async (@event, ex, failureLevels) => + { + if (failureLevels != FailureLevels.Ignore) + { + isCancel = true; + if (dispatchRelation.CancelHandlers.Any()) + { + await ExecuteEventCanceledHandlerAsync(serviceProvider, _logger, executionStrategy, dispatchRelation.CancelHandlers, @event); + } + else + { + throw ex; + } + } + else + { + _logger?.LogWarning("----- Publishing event {@Event} error rollback is ignored: message id: {messageId} -----", @event, @event.Id); + } + }); + } + } + + private async Task ExecuteEventCanceledHandlerAsync(IServiceProvider serviceProvider, + ILogger? logger, + IExecutionStrategy executionStrategy, + IEnumerable cancelHandlers, + TEvent @event) + where TEvent : IEvent + { + StrategyOptions strategyOptions = new StrategyOptions(); + foreach (var cancelHandler in cancelHandlers) + { + strategyOptions.SetStrategy(cancelHandler); + await executionStrategy.ExecuteAsync(strategyOptions, @event, async @event => + { + logger?.LogDebug("----- Publishing event {@Event} rollback start: message id: {messageId} -----", @event, @event.Id); + await cancelHandler.ExcuteAction(serviceProvider, @event); + }, (@event, ex, failureLevels) => + { + if (failureLevels != FailureLevels.Ignore) + { + throw ex; + } + logger?.LogWarning("----- Publishing event {@Event} rollback error ignored: message id: {messageId} -----", @event, @event.Id); + return Task.CompletedTask; + }); + } + } + + protected void AddRelationNetwork(Type parameterType, EventHandlerAttribute handler) + { + _sharingRelationNetwork!.Add(parameterType, handler); + } + + protected IEnumerable GetAddServiceTypeList() => _sharingRelationNetwork!.HandlerRelationNetwork + .Concat(_sharingRelationNetwork.CancelRelationNetwork) + .SelectMany(relative => relative.Value) + .Where(dispatchHandler => dispatchHandler.InvokeDelegate != null) + .Select(dispatchHandler => dispatchHandler.InstanceType).Distinct(); + + protected void Build() => _sharingRelationNetwork!.Build(); + + protected bool IsSagaMode(Type handlerType, MethodInfo method) => + typeof(IEventHandler<>).IsGenericInterfaceAssignableFrom(handlerType) && method.Name.Equals(nameof(IEventHandler.HandleAsync)) || + typeof(ISagaEventHandler<>).IsGenericInterfaceAssignableFrom(handlerType) && method.Name.Equals(nameof(ISagaEventHandler.CancelAsync)); +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs new file mode 100644 index 000000000..952003208 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Dispatch/SagaDispatcher.cs @@ -0,0 +1,118 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal.Dispatch; + +internal class SagaDispatcher : DispatcherBase +{ + public SagaDispatcher(IServiceCollection services, Assembly[] assemblies, bool forceInit = false) : base(services, assemblies, forceInit) { } + + public SagaDispatcher Build(ServiceLifetime lifetime) + { + AddSagaDispatchRelation(_services, typeof(IEventHandler<>), lifetime); + AddSagaDispatchRelation(_services, typeof(ISagaEventHandler<>), lifetime); + return this; + } + + private IServiceCollection AddSagaDispatchRelation(IServiceCollection services, Type eventBusHandlerType, ServiceLifetime lifetime) + { + foreach (var item in GetAddSagaServices(eventBusHandlerType)) + { + services.Add(item.ServiceType, item.ImplementationType, lifetime); + AddSagaRelationNetwork(item.ImplementationType); + } + return services; + } + + private void AddSagaRelationNetwork(Type eventBusHandlerType) + { + var eventHandlers = GetSagaHandlers(eventBusHandlerType); + var eventHandler = eventHandlers.Where(x => x.Order != int.MaxValue).FirstOrDefault(); + var actualOrder = eventHandler?.Order ?? int.MaxValue; + if (actualOrder < 0) + { + throw new ArgumentOutOfRangeException("The order must be greater than or equal to 0"); + } + foreach (var handler in eventHandlers) + { + if (actualOrder != handler.Order) + { + handler.Order = actualOrder; + } + AddRelationNetwork(handler.EventType, handler); + } + } + + private List GetSagaHandlers(Type eventBusHandlerType) + { + var methods = eventBusHandlerType.GetMethods(); + Type? eventType = null!; + List eventHandlers = new(); + foreach (var method in methods) + { + var parameters = method.GetParameters(); + if (parameters != null && parameters.Length == 1 && parameters.All(parameter => typeof(IEvent).IsAssignableFrom(parameter.ParameterType) && !typeof(IIntegrationEvent).IsAssignableFrom(parameter.ParameterType)) && IsSagaMode(eventBusHandlerType, method)) + { + var attribute = method.GetCustomAttributes(typeof(EventHandlerAttribute), true).FirstOrDefault(); + var handler = attribute != null ? attribute as EventHandlerAttribute : null; + if (eventType == null) + { + eventType = parameters.Select(x => x.ParameterType).FirstOrDefault()!; + } + + if (handler is null) + { + handler = new EventHandlerAttribute(); + } + handler.ActionMethodInfo = method; + handler.InstanceType = eventBusHandlerType; + handler.EventType = eventType; + handler.IsCancel = method.Name.Equals(nameof(ISagaEventHandler.CancelAsync)); + eventHandlers.Add(handler); + } + } + + //In saga mode, when the user sets an Order for either HandlerAsync or CancelAsync + //the default int.MaxValue Order will be overridden + //but when the Order of HandlerAsync and CancelAsync are inconsistent + //an error will be displayed + if (eventHandlers.Where(handler => handler.Order != int.MaxValue).Select(handler => handler.Order).Distinct().Count() > 1) + { + throw new ArgumentException($"In saga mode {nameof(IEventHandler.HandleAsync)} needs to be the same as {nameof(ISagaEventHandler.CancelAsync)} Order"); + } + return eventHandlers; + } + + private List<(Type ServiceType, Type ImplementationType)> GetAddSagaServices(Type eventBusHandlerType) + { + List<(Type ServiceType, Type ImplementationType)> list = new(); + var serviceTypeAndImplementationInfo = GetSagaServiceTypeAndImplementations(eventBusHandlerType); + foreach (var serviceType in serviceTypeAndImplementationInfo.ServiceTypeList) + { + var implementationTypes = serviceTypeAndImplementationInfo.ImplementationType.Where(implementationType => serviceType.IsAssignableFrom(implementationType)).ToList(); + + foreach (var implementationType in implementationTypes) + { + list.Add((serviceType, implementationType)); + } + } + + return list; + } + + private (List ServiceTypeList, List ImplementationType) GetSagaServiceTypeAndImplementations(Type eventBusHandlerType) + { + var concretions = new List(); + var interfaces = new List(); + foreach (var type in _assemblies.SelectMany(a => a.DefinedTypes).Where(t => !t.IsGeneric())) + { + if (type.IsConcrete()) + { + concretions.Add(type); + } + + if (eventBusHandlerType.IsGenericInterfaceAssignableFrom(type) && !interfaces.Contains(type)) + { + interfaces.AddRange(type.GetInterfaces()); + } + } + return (interfaces, concretions); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs new file mode 100644 index 000000000..edc887265 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/DispatcherExtensions.cs @@ -0,0 +1,24 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal; + +internal static class DispatcherExtensions +{ + public static IServiceCollection Add(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime) + { + services.Add(new ServiceDescriptor(serviceType, implementationType, lifetime)); + return services; + } + + public static IServiceCollection TryAdd(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime) + { + services.TryAdd(new ServiceDescriptor(serviceType, implementationType, lifetime)); + return services; + } + + public static bool IsGeneric(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition || type.GetTypeInfo().ContainsGenericParameters; + + public static bool IsConcrete(this Type type) => !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; + + public static bool IsGenericInterfaceAssignableFrom(this Type eventHandlerType, Type type) => + type.IsConcrete() && + type.GetInterfaces().Any(t => t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == eventHandlerType); +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs new file mode 100644 index 000000000..10e54b352 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Expressions/InvokeBuilder.cs @@ -0,0 +1,56 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal.Expressions; + +internal delegate Task TaskInvokeDelegate(object target, params object[] parameters); + +internal delegate void VoidInvokeDelegate(object target, object[] parameters); + +internal class InvokeBuilder +{ + public static TaskInvokeDelegate Build(MethodInfo methodInfo, Type targetType) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (var i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetType); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" + // Create function + if (methodCall.Type == typeof(void)) + { + var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); + var voidExecutor = lambda.Compile(); + return delegate (object target, object[] parameters) + { + voidExecutor(target, parameters); + return Task.CompletedTask; + }; + } + else if (methodCall.Type == typeof(Task)) + { + // must coerce methodCall to match ActionExecutor signature + var castMethodCall = Expression.Convert(methodCall, typeof(Task)); + var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); + return lambda.Compile(); + } + else + { + throw new NotSupportedException($"The return type of the [{methodInfo.Name}] method must be Task or void"); + } + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs new file mode 100644 index 000000000..ccbed04be --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Internal/Middleware/TransactionMiddleware.cs @@ -0,0 +1,49 @@ +namespace Masa.Contrib.Dispatcher.Events.Internal.Middleware; + +public class TransactionMiddleware : IMiddleware + where TEvent : notnull, IEvent +{ + private readonly IUnitOfWork? _unitOfWork; + + public TransactionMiddleware(IUnitOfWork? unitOfWork = null) + { + _unitOfWork = unitOfWork; + } + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + try + { + await next(); + + if (_unitOfWork is { EntityState: EntityState.Changed }) + { + await _unitOfWork.SaveChangesAsync(); + } + if (IsUseTransaction(@event, out ITransaction? transaction)) + { + await transaction!.UnitOfWork!.CommitAsync(); + } + } + catch (Exception) + { + if (IsUseTransaction(@event, out ITransaction? transaction) && !transaction!.UnitOfWork!.DisableRollbackOnFailure) + { + await transaction.UnitOfWork!.RollbackAsync(); + } + throw; + } + } + + private bool IsUseTransaction(TEvent @event, out ITransaction? transaction) + { + if (@event is ITransaction { UnitOfWork: { UseTransaction: true, TransactionHasBegun: true, CommitState: CommitState.UnCommited } } transactionEvent) + { + transaction = transactionEvent; + return true; + } + + transaction = null; + return false; + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj new file mode 100644 index 000000000..463205441 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Masa.Contrib.Dispatcher.Events.csproj @@ -0,0 +1,22 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs new file mode 100644 index 000000000..43646d732 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatchRelationOptions.cs @@ -0,0 +1,21 @@ +namespace Masa.Contrib.Dispatcher.Events.Options; + +public class DispatchRelationOptions +{ + public EventHandlerAttribute Handler { get; set; } = new(); + + public IEnumerable CancelHandlers { get; set; } = new List(); + + public DispatchRelationOptions() { } + + public DispatchRelationOptions(EventHandlerAttribute handler) : this() => Handler = handler; + + public void AddCancelHandler(IEnumerable cancelHandlers) + => CancelHandlers = cancelHandlers; + + public bool IsCancelHandler(EventHandlerAttribute cancelHandler) + { + return Handler.FailureLevels == FailureLevels.ThrowAndCancel && cancelHandler.Order <= Handler.Order + || Handler.FailureLevels == FailureLevels.Throw && cancelHandler.Order < Handler.Order; + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs new file mode 100644 index 000000000..189461d55 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Options/DispatcherOptions.cs @@ -0,0 +1,36 @@ +namespace Masa.Contrib.Dispatcher.Events.Options; + +public class DispatcherOptions : IDispatcherOptions +{ + private Assembly[] _assemblies = Array.Empty(); + + public Assembly[] Assemblies + { + get => _assemblies; + set + { + _assemblies = value; + if (_assemblies == null || _assemblies.Length == 0) + { + throw new ArgumentNullException(nameof(_assemblies)); + } + AllEventTypes = _assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) + .ToList(); + + UnitOfWorkRelation = AllEventTypes.ToDictionary(type => type, IsSupportUnitOfWork); + } + } + + private bool IsSupportUnitOfWork(Type eventType) + => typeof(ITransaction).IsAssignableFrom(eventType) && !typeof(IDomainQuery<>).IsGenericInterfaceAssignableFrom(eventType); + + internal Dictionary UnitOfWorkRelation { get; set; } = new(); + + public IEnumerable AllEventTypes { get; private set; } + + public IServiceCollection Services { get; } + + public DispatcherOptions(IServiceCollection services) => Services = services; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.md b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.md new file mode 100644 index 000000000..048c2aac7 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.md @@ -0,0 +1,194 @@ +[ไธญ](README.zh-CN.md) | EN + +## EventBus + +Example๏ผš + +```c# +Install-Package Masa.Contrib.Dispatcher.Events +``` + +##### Basic usage๏ผš + +1. Add EventBus + +```c# +var builder = WebApplication.CreateBuilder(args); +var app = builder.Services + .AddEventBus() + //TODO +``` + +2. Custom Event + +```C# +public class TransferEvent : Event +{ + public string Account { get; set; } = default!; + + public string ReceiveAccount { get; set; } = default!; + + public decimal Money{ get; set; } +} +``` + +3. Send Event + +```C# +IEventBus eventBus;//Get IEventBus through DI +await eventBus.PublishAsync(new TransferEvent());//Send Event +``` + +4. Define Handler + +```C# +public class TransferHandler +{ + [EventHandler] + public Task TransferAsync(TransferEvent @event) + { + //TODO Simulated transfer business + } +} +``` + +Or use the way to implement the interface: + +```C# +public class TransferHandler : IEventHandler +{ + public Task HandleAsync(TransferEvent @event) + { + //TODO Simulated transfer business + } +} +``` + +##### Advanced usage: + +1. Handler arrangement: + +```C# +public class TransferHandler +{ + [EventHandler(1)] + public Task CheckBalanceAsync(TransferEvent @event) + { + //TODO Simulate check balance + } + + [EventHandler(2)] + public Task DeductionBalanceAsync(RegisterUserEvent @event) + { + //TODO Simulated deduction balance + } +} +``` + +2. Support Saga mode + +If there is an error in sending the deducted balance, try again 3 times. If it still fails, check whether the balance is deducted and ensure that there is no deduction and notify the transfer failure + +```C# +public class TransferHandler +{ + [EventHandler(1)] + public Task CheckBalanceAsync(TransferEvent @event) + { + //TODO Simulate check balance + } + + [EventHandler(1, FailureLevels.Ignore, false, true)] + public Task NotificationTransferFailedAsync(TransferEvent @event) + { + //TODO Simulation notification transfer failed + } + + [EventHandler(2, FailureLevels.ThrowAndCancel, true, 3)] + public Task DeductionBalanceAsync(TransferEvent @event) + { + //TODO Simulated deduction balance + throw new Exception("Failed to deduct balance"); + } + + [EventHandler(2, FailureLevels.Ignore, false, true)] + public Task CancelDeductionBalanceAsync(TransferEvent @event) + { + //TODO Idempotent check to ensure that the balance has not been deducted + } +} +``` + +> Execution order: CheckBalanceAsync -> DeductionBalanceAsync (execute 1 time, retry 3 times) -> CancelDeductionBalanceAsync -> NotificationTransferFailedAsync + +Or use the way to implement the interface + +```C# +public class TransferHandler : ISagaEventHandler +{ + [EventHandler(1, FailureLevels.ThrowAndCancel, true, 3)] + public Task HandleAsync(TransferEvent @event) + { + //TODO Simulate check balance deduction balance + } + + [EventHandler(1, FailureLevels.Ignore, false, true)] + public Task CancelAsync(TransferEvent @event) + { + //TODO Idempotent verification and notification of transfer failure + } +} +``` + +> Tip: +> The method where the Handler is located only supports one parameter +> The return type of the method where the Handler is located only supports Task or void two types +> The parameters of the constructor of the class where the Handler is located must support getting from DI + +3. Support Middleware + + 1. Custom Middleware +```C# +public class LoggingMiddleware + : IMiddleware where TEvent : notnull, IEvent +{ + private readonly ILogger> _logger; + public LoggingMiddleware(ILogger> logger) => _logger = logger; + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + _logger.LogInformation("----- Handling command {EventName} ({@Event})", typeof(TEvent).FullName, @event); + await next(); + } +} +``` + 2. Enable custom Middleware + + +```C# +builder.Services + .AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)) +``` + +4. Support Transaction + +> Used in conjunction with Contracts.EF and UnitOfWork, when Event implements ITransaction, the transaction will be automatically opened after the first CUD is executed, and the transaction will be submitted after all Handlers are executed. When an exception occurs in the transaction, the transaction will be automatically rolled back. + +##### Summarize + +IEventBus is the core of the event bus. It can be used with Cqrs, Uow, Masa.Contrib.Ddd.Domain.Repository.EF to automatically execute SaveChange (enable UoW) and Commit (enable UoW without closing transaction) operations after sending Command, And support to roll back the transaction after an exception occurs + +> Question 1. Publishing events through eventBus, Handler error -> and handler throw exception + + > 1. Check custom events or inherited classes to make sure ITransaction is implemented + > 2. Confirm that UoW is used + > 3. Make sure the UseTransaction property of UnitOfWork is false + > 4. Make sure that the DisableRollbackOnFailure property of UnitOfWork is true + +> Question 2. Under what circumstances will SaveChange be automatically saved -> When auto call SaveChange? + + > Use UoW and Masa.Contrib.Ddd.Domain.Repository.EF, and use the Add, Update, Delete operations provided by IRepository, publish events through EventBus, and automatically execute SaveChange after executing EventHandler + +> Question 3. If the SaveChange method of UoW is manually called in EventHandler to save, will the framework also save automatically? + + > If the SaveChange method of UoW is manually called in the EventHandler to save, and the Add, Update, and Delete operations provided by IRepository are not used afterward, the SaveChange operation will not be executed twice after the EventHandler execution ends, but if the UoW is manually called. After the SaveChange method is saved and continue to use the Add, Update, and Delete operations provided by IRepository, the framework will call the SaveChange operation again to ensure that the data is saved successfully. \ No newline at end of file diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.zh-CN.md b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.zh-CN.md new file mode 100644 index 000000000..2f348b0e7 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/README.zh-CN.md @@ -0,0 +1,194 @@ +ไธญ | [EN](README.md) + +## EventBus + +็”จไพ‹๏ผš + +```c# +Install-Package Masa.Contrib.Dispatcher.Events +``` + +##### ๅŸบๆœฌ็”จๆณ•๏ผš + +1. ๆทปๅŠ EventBus + +```c# +var builder = WebApplication.CreateBuilder(args); +var app = builder.Services + .AddEventBus() + //TODO +``` + +2. ่‡ชๅฎšไน‰Event + +```C# +public class TransferEvent : Event +{ + public string Account { get; set; } = default!; + + public string ReceiveAccount { get; set; } = default!; + + public decimal Money{ get; set; } +} +``` + +3. ๅ‘้€Event + +```C# +IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus +await eventBus.PublishAsync(new TransferEvent());//ๅ‘้€Event +``` + +4. ๅฎšไน‰Handler + +```C# +public class TransferHandler +{ + [EventHandler] + public Task TransferAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿ่ฝฌ่ดฆไธšๅŠก + } +} +``` + +ๆˆ–ไฝฟ็”จๅฎž็ŽฐๆŽฅๅฃ็š„ๆ–นๅผ๏ผš + +```C# +public class TransferHandler : IEventHandler +{ + public Task HandleAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿ่ฝฌ่ดฆไธšๅŠก + } +} +``` + +##### ้ซ˜็บง็”จๆณ•๏ผš + +1. Handler็ผ–ๆŽ’๏ผš + +```C# +public class TransferHandler +{ + [EventHandler(1)] + public Task CheckBalanceAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿๆฃ€ๆŸฅไฝ™้ข + } + + [EventHandler(2)] + public Task DeductionBalanceAsync(RegisterUserEvent @event) + { + //TODO ๆจกๆ‹Ÿๆ‰ฃๅ‡ไฝ™้ข + } +} +``` + +2. ๆ”ฏๆŒSagaๆจกๅผ + +ๅ‡ๅฆ‚ๆ‰ฃๅ‡ไฝ™้ขๅ‘้€ๅ‡บ้”™๏ผŒๅˆ™้‡่ฏ•3ๆฌก๏ผŒๅฆ‚ๆžœไป็„ถๅคฑ่ดฅๅˆ™ๆ ก้ชŒไฝ™้ขๆ˜ฏๅฆๆ‰ฃๅ‡๏ผŒ็กฎไฟๆ— ๆ‰ฃๅ‡ๅŽ้€š็Ÿฅ่ฝฌ่ดฆๅคฑ่ดฅ + +```C# +public class TransferHandler +{ + [EventHandler(1)] + public Task CheckBalanceAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿๆฃ€ๆŸฅไฝ™้ข + } + + [EventHandler(1, FailureLevels.Ignore, false, true)] + public Task NotificationTransferFailedAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿ้€š็Ÿฅ่ฝฌ่ดฆๅคฑ่ดฅ + } + + [EventHandler(2, FailureLevels.ThrowAndCancel, true, 3)] + public Task DeductionBalanceAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿๆ‰ฃๅ‡ไฝ™้ข + throw new Exception("ๆ‰ฃๅ‡ไฝ™้ขๅคฑ่ดฅ"); + } + + [EventHandler(2, FailureLevels.Ignore, false, true)] + public Task CancelDeductionBalanceAsync(TransferEvent @event) + { + //TODO ๅน‚็ญ‰ๆ ก้ชŒ๏ผŒ็กฎไฟไฝ™้ขๆœชๆ‰ฃๅ‡ + } +} +``` + +> ๆ‰ง่กŒ้กบๅบ๏ผš CheckBalanceAsync -> DeductionBalanceAsync ๏ผˆๆ‰ง่กŒ1ๆฌก๏ผŒ้‡่ฏ•3ๆฌก๏ผ‰-> CancelDeductionBalanceAsync -> NotificationTransferFailedAsync + +ๆˆ–่€…ไฝฟ็”จๅฎž็ŽฐๆŽฅๅฃ็š„ๆ–นๅผ + +```C# +public class TransferHandler : ISagaEventHandler +{ + [EventHandler(1, FailureLevels.ThrowAndCancel, true, 3)] + public Task HandleAsync(TransferEvent @event) + { + //TODO ๆจกๆ‹Ÿๆฃ€ๆŸฅไฝ™้ขๆ‰ฃๅ‡ไฝ™้ข + } + + [EventHandler(1, FailureLevels.Ignore, false, true)] + public Task CancelAsync(TransferEvent @event) + { + //TODO ๅน‚็ญ‰ๆ ก้ชŒๅนถ้€š็Ÿฅ่ฝฌ่ดฆๅคฑ่ดฅ + } +} +``` + +> ๆณจๆ„๏ผš +> Handlerๆ‰€ๅœจ็š„ๆ–นๆณ•ไป…ๆ”ฏๆŒไธ€ไธชๅ‚ๆ•ฐ +> Handlerๆ‰€ๅœจ็š„ๆ–นๆณ•่ฟ”ๅ›ž็ฑปๅž‹ไป…ๆ”ฏๆŒTaskๆˆ–voidไธค็ง็ฑปๅž‹ +> Handlerๆ‰€ๅœจ็š„็ฑป็š„ๆž„้€ ๅ‡ฝๆ•ฐ็š„ๅ‚ๆ•ฐๅฟ…้กปๆ”ฏๆŒไปŽDIไธญ่Žทๅ– + +3. ๆ”ฏๆŒMiddleware + + 1. ่‡ชๅฎšไน‰Middleware +```C# +public class LoggingMiddleware + : IMiddleware where TEvent : notnull, IEvent +{ + private readonly ILogger> _logger; + public LoggingMiddleware(ILogger> logger) => _logger = logger; + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + _logger.LogInformation("----- Handling command {EventName} ({@Event})", typeof(TEvent).FullName, @event); + await next(); + } +} +``` + 2. ๅฏ็”จ่‡ชๅฎšไน‰Middleware + + +```C# +builder.Services + .AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)) +``` + +4. ๆ”ฏๆŒTransaction + +> ้…ๅˆMASA.Contrib.Ddd.Domain.Repository.EF.Repositoryใ€UnitOfWorkไฝฟ็”จ๏ผŒๅฝ“Eventๅฎž็Žฐไบ†ITransaction๏ผŒไผšๅœจๆ‰ง่กŒAddใ€Updateใ€Deleteๆ–นๆณ•ๆ—ถ่‡ชๅŠจๅผ€ๅฏไบ‹ๅŠก๏ผŒไธ”ๅœจHandlerๅ…จ้ƒจๆ‰ง่กŒๅŽๆไบคไบ‹ๅŠก๏ผŒๅฝ“ไบ‹ๅŠกๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒไผš่‡ชๅŠจๅ›žๆปšไบ‹ๅŠก + +##### ๆ€ป็ป“ + +IEventBusๆ˜ฏไบ‹ไปถๆ€ป็บฟ็š„ๆ ธๅฟƒ๏ผŒ้…ๅˆCqrsใ€Uowใ€Masa.Contrib.Ddd.Domain.Repository.EFไฝฟ็”จ๏ผŒๅฏๅฎž็Žฐ่‡ชๅŠจๆ‰ง่กŒSaveChange๏ผˆๅฏ็”จUoW๏ผ‰ไธŽCommit๏ผˆๅฏ็”จUoWไธ”ๆ— ๅ…ณ้—ญไบ‹ๅŠก๏ผ‰ๆ“ไฝœ๏ผŒๅนถๆ”ฏๆŒๅ‡บ็Žฐๅผ‚ๅธธๅŽ๏ผŒๅ›žๆปšไบ‹ๅŠก + +> ้—ฎ้ข˜1. ้€š่ฟ‡eventBusๅ‘ๅธƒไบ‹ไปถ๏ผŒHandlerๅ‡บ้”™๏ผŒไฝ†ๆ•ฐๆฎไพ็„ถไฟๅญ˜ๅˆฐๆ•ฐๆฎๅบ“ไธญ๏ผŒไบ‹ๅŠกๅนถๆœชๅ›žๆปš + + > 1. ๆฃ€ๆŸฅ่‡ชๅฎšไน‰ไบ‹ไปถๆˆ–็ปงๆ‰ฟ็ฑป๏ผŒ็กฎไฟๅทฒ็ปๅฎž็ŽฐITransaction + > 2. ็กฎ่ฎคๅทฒไฝฟ็”จUoW + > 3. ็กฎ่ฎคUnitOfWork็š„UseTransactionๅฑžๆ€งไธบfalse + > 4. ็กฎ่ฎคUnitOfWork็š„DisableRollbackOnFailureๅฑžๆ€งไธบtrue + +> ้—ฎ้ข˜2. ไป€ไนˆๆ—ถๅ€™่‡ชๅŠจ่ฐƒ็”จSaveChanges + + > ไฝฟ็”จUoWไธ”ไฝฟ็”จไบ†MASA.Contrib.Ddd.Domain.Repository.EF๏ผŒๅนถไธ”ไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒ้€š่ฟ‡EventBusๅ‘ๅธƒไบ‹ไปถ๏ผŒๅœจๆ‰ง่กŒEventHandlerๅŽไผš่‡ชๅŠจๆ‰ง่กŒSaveChange + +> ้—ฎ้ข˜3. ๅฆ‚ๆžœๅœจEventHandlerไธญๆ‰‹ๅŠจ่ฐƒ็”จUoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜๏ผŒ้‚ฃๆก†ๆžถ่ฟ˜ไผš่‡ชๅŠจไฟๅญ˜ๅ—๏ผŸ + + > ๅฆ‚ๆžœๅœจEventHandlerไธญๆ‰‹ๅŠจ่ฐƒ็”จไบ†UoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜๏ผŒไธ”ไน‹ๅŽๅนถๆœชๅ†ไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒๅˆ™ๅœจEventHandlerๆ‰ง่กŒ็ป“ๆŸๅŽไธไผšไบŒๆฌกๆ‰ง่กŒSaveChangeๆ“ไฝœ๏ผŒไฝ†ๅฆ‚ๆžœๅœจๆ‰‹ๅŠจ่ฐƒ็”จUoW็š„SaveChangeๆ–นๆณ•ไฟๅญ˜ๅŽๅˆ็ปง็ปญไฝฟ็”จIRepositoryๆไพ›็š„Addใ€Updateใ€Deleteๆ“ไฝœ๏ผŒๅˆ™ๆก†ๆžถไผšๅ†ๆฌก่ฐƒ็”จSaveChangeๆ“ไฝœไปฅ็กฎไฟๆ•ฐๆฎไฟๅญ˜ๆˆๅŠŸ \ No newline at end of file diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..3162909fa --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/ServiceCollectionExtensions.cs @@ -0,0 +1,61 @@ +namespace Masa.Contrib.Dispatcher.Events; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddEventBus( + this IServiceCollection services, + Action? options = null) + => services.AddEventBus(ServiceLifetime.Scoped, options); + + public static IServiceCollection AddEventBus( + this IServiceCollection services, + ServiceLifetime lifetime, + Action? options = null) + { + if (services.Any(service => service.ImplementationType == typeof(EventBusProvider))) return services; + services.AddSingleton(); + + DispatcherOptions dispatcherOptions = new DispatcherOptions(services); + options?.Invoke(dispatcherOptions); + if (dispatcherOptions.Assemblies.Length == 0) + { + dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + } + services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); + + services.AddSingleton(new SagaDispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); + services.AddSingleton(new Internal.Dispatch.Dispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); + services.TryAdd(typeof(IExecutionStrategy), typeof(ExecutionStrategy), ServiceLifetime.Singleton); + services.AddTransient(typeof(IMiddleware<>), typeof(TransactionMiddleware<>)); + services.AddScoped(typeof(IEventBus), typeof(EventBus)); + return services; + } + + public static IServiceCollection AddTestEventBus(this IServiceCollection services, ServiceLifetime lifetime, + Action? options = null) + { + if (services.Any(service => service.ImplementationType == typeof(EventBusProvider))) return services; + services.AddSingleton(); + + DispatcherOptions dispatcherOptions = new DispatcherOptions(services); + options?.Invoke(dispatcherOptions); + if (dispatcherOptions.Assemblies.Length == 0) + { + dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + } + + services.AddSingleton(typeof(IOptions), serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); + services.AddSingleton(new SagaDispatcher(services, dispatcherOptions.Assemblies, true).Build(lifetime)); + services.AddSingleton(new Internal.Dispatch.Dispatcher(services, dispatcherOptions.Assemblies).Build(lifetime)); + services.TryAdd(typeof(IExecutionStrategy), typeof(ExecutionStrategy), ServiceLifetime.Singleton); + services.AddTransient(typeof(IMiddleware<>), typeof(TransactionMiddleware<>)); + services.AddScoped(typeof(IEventBus), typeof(EventBus)); + + return services; + } + + private class EventBusProvider + { + + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs new file mode 100644 index 000000000..3c35cc028 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/ExecutionStrategy.cs @@ -0,0 +1,45 @@ +namespace Masa.Contrib.Dispatcher.Events.Strategies; + +public class ExecutionStrategy : IExecutionStrategy +{ + private readonly ILogger? _logger; + + public ExecutionStrategy(ILogger? logger = null) => _logger = logger; + + public async Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @event, Func func, Func cancel) + where TEvent : IEvent + { + int retryTimes = 0; + + Exception exception = null!; + while (strategyOptions.IsRetry(retryTimes)) + { + try + { + if (retryTimes > 0) + { + _logger?.LogWarning("----- Error Publishing event {@Event} start: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); + } + await func.Invoke(@event); + return; + } + catch (Exception ex) + { + if (retryTimes > 0) + { + _logger?.LogWarning("----- Error Publishing event {@Event} finish: The {retries}th retrying consume a message failed. message id: {messageId} -----", @event, retryTimes, @event.Id); + } + else + { + _logger?.LogWarning(ex, "----- Error Publishing event {@Event}: after {retries}th executions and we will stop retrying. message id: {messageId} -----", @event, strategyOptions.MaxRetryCount, @event.Id); + } + exception = ex; + retryTimes++; + } + } + + //perform the cancel handler + + await cancel(@event, exception, strategyOptions.FailureLevels); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs new file mode 100644 index 000000000..a270bbff0 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/IExecutionStrategy.cs @@ -0,0 +1,7 @@ +namespace Masa.Contrib.Dispatcher.Events.Strategies; + +public interface IExecutionStrategy +{ + Task ExecuteAsync(StrategyOptions strategyOptions, TEvent @event, Func func, Func cancel) + where TEvent : IEvent; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs new file mode 100644 index 000000000..0e814a8f5 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/Strategies/StrategyOptions.cs @@ -0,0 +1,19 @@ +namespace Masa.Contrib.Dispatcher.Events.Strategies; + +public class StrategyOptions +{ + /// + /// The maximum number of retry attempts. + /// + public int MaxRetryCount { get; set; } + + public FailureLevels FailureLevels { get; set; } + + public bool IsRetry(int retryTimes) => retryTimes <= MaxRetryCount; + + public void SetStrategy(EventHandlerAttribute dispatchHandler) + { + MaxRetryCount = dispatchHandler.ActualRetryTimes; ; + FailureLevels = dispatchHandler.FailureLevels; + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.Events/_Imports.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/_Imports.cs new file mode 100644 index 000000000..13726e3b8 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.Events/_Imports.cs @@ -0,0 +1,18 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Ddd.Domain.Events; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.Contrib.Dispatcher.Events.Enums; +global using Masa.Contrib.Dispatcher.Events.Internal; +global using Masa.Contrib.Dispatcher.Events.Internal.Dispatch; +global using Masa.Contrib.Dispatcher.Events.Internal.Expressions; +global using Masa.Contrib.Dispatcher.Events.Internal.Middleware; +global using Masa.Contrib.Dispatcher.Events.Options; +global using Masa.Contrib.Dispatcher.Events.Strategies; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using System.Linq.Expressions; +global using System.Reflection; +global using System.Text.Json.Serialization; diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..2923ee559 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/DispatcherOptionsExtensions.cs @@ -0,0 +1,22 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public static class DispatcherOptionsExtensions +{ + public static IDispatcherOptions UseDaprEventBus( + this IDispatcherOptions options, + string daprPubsubName = "pubsub", + Action? builder = null) + where TIntegrationEventLogService : class, IIntegrationEventLogService + { + if (options.Services == null) + { + throw new ArgumentNullException(nameof(options.Services)); + } + + options.Services.TryAddDaprEventBus(builder, dispatcherOptions => + { + dispatcherOptions.PubSubName = daprPubsubName; + }); + return options; + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs new file mode 100644 index 000000000..0ac9181fc --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessingServer.cs @@ -0,0 +1,6 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public interface IProcessingServer +{ + Task ExecuteAsync(CancellationToken stoppingToken); +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs new file mode 100644 index 000000000..983cf5fd0 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IProcessor.cs @@ -0,0 +1,13 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public interface IProcessor +{ + Task ExecuteAsync(CancellationToken stoppingToken); + + /// + /// Easy to switch between background tasks + /// + /// unit: seconds + /// + Task DelayAsync(int delay); +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs new file mode 100644 index 000000000..aa4ba9b08 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEvent.cs @@ -0,0 +1,18 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public abstract record IntegrationEvent(Guid Id, DateTime CreationTime) : IIntegrationEvent +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } + + [JsonIgnore] + public abstract string Topic { get; set; } + + public IntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs new file mode 100644 index 000000000..ab384fa6b --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventBus.cs @@ -0,0 +1,100 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public class IntegrationEventBus : IIntegrationEventBus +{ + private readonly DispatcherOptions _dispatcherOptions; + private readonly DaprClient _dapr; + private readonly ILogger? _logger; + private readonly IIntegrationEventLogService _eventLogService; + private readonly IOptionsMonitor? _appConfig; + private readonly string _daprPubsubName; + private readonly IEventBus? _eventBus; + private readonly IUnitOfWork? _unitOfWork; + + public IntegrationEventBus(IOptions options, + DaprClient dapr, + IIntegrationEventLogService eventLogService, + IOptionsMonitor? appConfig = null, + ILogger? logger = null, + IEventBus? eventBus = null, + IUnitOfWork? unitOfWork = null) + { + _dispatcherOptions = options.Value; + _dapr = dapr; + _eventLogService = eventLogService; + _appConfig = appConfig; + _logger = logger; + _daprPubsubName = options.Value.PubSubName; + _eventBus = eventBus; + _unitOfWork = unitOfWork; + } + + public IEnumerable GetAllEventTypes() => + _eventBus == null + ? _dispatcherOptions.AllEventTypes + : _dispatcherOptions.AllEventTypes.Concat(_eventBus.GetAllEventTypes()).Distinct(); + + public async Task PublishAsync(TEvent @event) + where TEvent : IEvent + { + if (@event is IIntegrationEvent integrationEvent) + { + await PublishIntegrationAsync(integrationEvent); + } + else if (_eventBus != null) + { + await _eventBus.PublishAsync(@event); + } + else + { + throw new NotSupportedException(nameof(@event)); + } + } + + private async Task PublishIntegrationAsync(TEvent @event) + where TEvent : IIntegrationEvent + { + if (@event.UnitOfWork == null && _unitOfWork != null) + @event.UnitOfWork = _unitOfWork; + + var topicName = @event.Topic; + if (@event.UnitOfWork != null && !@event.UnitOfWork.UseTransaction) + { + try + { + _logger?.LogDebug("----- Saving changes and integrationEvent: {IntegrationEventId}", @event.Id); + await _eventLogService.SaveEventAsync(@event, @event.UnitOfWork!.Transaction); + + _logger?.LogDebug( + "----- Publishing integration event: {IntegrationEventIdPublished} from {AppId} - ({IntegrationEvent})", @event.Id, + _appConfig?.CurrentValue.AppId ?? string.Empty, @event); + + await _eventLogService.MarkEventAsInProgressAsync(@event.Id); + + _logger?.LogDebug("Publishing event {Event} to {PubsubName}.{TopicName}", @event, _daprPubsubName, topicName); + await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); + + await _eventLogService.MarkEventAsPublishedAsync(@event.Id); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", + @event.Id, _appConfig?.CurrentValue.AppId ?? string.Empty, @event); + LocalQueueProcessor.Default.AddJobs(new IntegrationEventLogItem(@event.Id, @event.Topic, @event)); + await _eventLogService.MarkEventAsFailedAsync(@event.Id); + } + } + else + { + await _dapr.PublishEventAsync(_daprPubsubName, topicName, (dynamic)@event); + } + } + + public async Task CommitAsync(CancellationToken cancellationToken = default) + { + if (_unitOfWork is null) + throw new ArgumentNullException(nameof(IUnitOfWork), "You need to UseUoW when adding services"); + + await _unitOfWork.CommitAsync(cancellationToken); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs new file mode 100644 index 000000000..7d688a2f5 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/IntegrationEventHostedService.cs @@ -0,0 +1,20 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public class IntegrationEventHostedService : BackgroundService +{ + private readonly ILogger? _logger; + private readonly IProcessingServer _processingServer; + + public IntegrationEventHostedService(IProcessingServer processingServer, ILogger? logger) + { + _logger = logger; + _processingServer = processingServer; + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger?.LogDebug("----- IntegrationEvent background task is starting"); + + return _processingServer.ExecuteAsync(stoppingToken); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs new file mode 100644 index 000000000..d35eb3a99 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/IntegrationEventLogItem.cs @@ -0,0 +1,31 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; + +/// +/// Use the local queue to retry sending failed messages +/// +internal class IntegrationEventLogItem +{ + public Guid EventId { get; } + + public string Topic { get; } + + public DateTime CreationTime { get; } + + public int RetryCount { get; private set; } + + public object Event { get; } + + public IntegrationEventLogItem(Guid eventId, string topic, object @event) + { + EventId = eventId; + Topic = topic; + RetryCount = 0; + CreationTime = DateTime.UtcNow; + Event = @event; + } + + public void Retry() + { + this.RetryCount++; + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs new file mode 100644 index 000000000..359010c55 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Internal/LocalQueueProcessor.cs @@ -0,0 +1,60 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; + +internal class LocalQueueProcessor +{ + private readonly ConcurrentDictionary _retryEventLogs; + + public static ILogger? Logger; + public static readonly LocalQueueProcessor Default = new(); + + public LocalQueueProcessor() => _retryEventLogs = new(); + + public static void SetLogger(IServiceCollection services) + { + Logger = services.BuildServiceProvider().GetService>(); + } + + public void AddJobs(IntegrationEventLogItem items) + => _retryEventLogs.TryAdd(items.EventId, items); + + public void RemoveJobs(Guid eventId) + => _retryEventLogs.TryRemove(eventId, out _); + + public void RetryJobs(Guid eventId) + { + if (_retryEventLogs.TryGetValue(eventId, out IntegrationEventLogItem? item)) + { + item.Retry(); + } + } + + public bool IsExist(Guid eventId) + => _retryEventLogs.ContainsKey(eventId); + + public void Delete(int maxRetryTimes) + { + var eventLogItems = _retryEventLogs.Values.Where(log => log.RetryCount >= maxRetryTimes - 1).ToList(); + eventLogItems.ForEach(item => RemoveJobs(item.EventId)); + } + + public List RetrieveEventLogsFailedToPublishAsync(int maxRetryTimes, int retryBatchSize) + { + try + { + return _retryEventLogs + .Select(item => item.Value) + .Where(log => log.RetryCount < maxRetryTimes) + .OrderBy(log => log.RetryCount) + .ThenBy(log => log.CreationTime) + .Take(retryBatchSize) + .ToList(); + } + catch (Exception ex) + { + Logger?.LogWarning(ex, "... getting local retry queue error"); + + Thread.Sleep(TimeSpan.FromSeconds(2)); + return new List(); + } + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj new file mode 100644 index 000000000..69606c6e4 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.csproj @@ -0,0 +1,21 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs new file mode 100644 index 000000000..046e2cb30 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Options/DispatcherOptions.cs @@ -0,0 +1,158 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; + +public class DispatcherOptions : IDispatcherOptions +{ + private string _pubSubName = "pubsub"; + + public string PubSubName + { + get => _pubSubName; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentNullException(nameof(_pubSubName)); + } + + _pubSubName = value; + } + } + + /// + /// Local queue maximum number of retries + /// + public int LocalRetryTimes { get; set; } = 3; + + /// + /// maximum number of retries + /// Default is 10 + /// + public int MaxRetryTimes { get; set; } = 10; + + private int _failedRetryInterval = 60; + + /// + /// The interval at which db polls for failure messages. + /// Default is 60 seconds. + /// unit: seconds + /// + public int FailedRetryInterval + { + get => _failedRetryInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(FailedRetryInterval)); + + _failedRetryInterval = value; + } + } + + /// + /// Minimum execution retry interval + /// Default is 60 seconds. + /// + public int MinimumRetryInterval { get; set; } = 60; + + private int _localFailedRetryInterval = 3; + + /// + /// The interval at which the local queue is polled for failed messages. + /// Local queue does not rebuild after service crash + /// Default is 3 seconds. + /// unit: seconds + /// + public int LocalFailedRetryInterval + { + get => _localFailedRetryInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(LocalFailedRetryInterval)); + + _localFailedRetryInterval = value; + } + } + + /// + /// maximum number of retries per retry + /// + public int RetryBatchSize { get; set; } = 100; + + private int _cleaningLocalQueueExpireInterval = 60; + + /// + /// Delete local queue expired event interval + /// Default is 60 seconds + /// unit: seconds + /// + public int CleaningLocalQueueExpireInterval + { + get => _cleaningLocalQueueExpireInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(CleaningLocalQueueExpireInterval)); + + _cleaningLocalQueueExpireInterval = value; + } + } + + private int _cleaningExpireInterval = 300; + + /// + /// Delete expired event interval + /// Default is 300 seconds. + /// unit: seconds + /// + public int CleaningExpireInterval + { + get => _cleaningExpireInterval; + set + { + if (value <= 0) + throw new ArgumentException("must be greater than or equal to 0", nameof(CleaningExpireInterval)); + + _cleaningExpireInterval = value; + } + } + + /// + /// Expiration time, when the message status is successful and has expired, it will be deleted by the scheduled task + /// Default: ( 24 * 3600 )s + /// + public long PublishedExpireTime { get; set; } = 24 * 3600; + + /// + /// Bulk delete expired messages + /// + public int DeleteBatchCount { get; set; } = 1000; + + public Func? GetCurrentTime { get; set; } = null; + + public IServiceCollection Services { get; } + + private Assembly[] _assemblies = Array.Empty(); + + public Assembly[] Assemblies + { + get => _assemblies; + set + { + _assemblies = value; + if (_assemblies == null || _assemblies.Length == 0) + { + throw new ArgumentNullException(nameof(_assemblies)); + } + + AllEventTypes = _assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) + .ToList(); + } + } + + public List AllEventTypes { get; private set; } + + public DispatcherOptions(IServiceCollection services) => Services = services; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs new file mode 100644 index 000000000..ad10e902a --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs @@ -0,0 +1,24 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class DeleteLocalQueueExpiresProcessor : ProcessorBase +{ + private readonly IOptions _options; + + public DeleteLocalQueueExpiresProcessor(IOptions options) + { + _options = options; + } + + /// + /// Delete expired events + /// + /// + /// + public override Task ExecuteAsync(CancellationToken stoppingToken) + { + LocalQueueProcessor.Default.Delete(_options.Value.LocalRetryTimes); + return Task.CompletedTask; + } + + public override int Delay => _options.Value.CleaningLocalQueueExpireInterval; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs new file mode 100644 index 000000000..35bcf2745 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs @@ -0,0 +1,32 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class DeletePublishedExpireEventProcessor : ProcessorBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptions _options; + + public DeletePublishedExpireEventProcessor( + IServiceProvider serviceProvider, + IOptions options) + { + _serviceProvider = serviceProvider; + _options = options; + } + + /// + /// Delete expired events + /// + /// + /// + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var logService = scope.ServiceProvider.GetRequiredService(); + var expireDate = (_options.Value.GetCurrentTime?.Invoke() ?? DateTime.UtcNow).AddSeconds(-_options.Value.PublishedExpireTime); + await logService.DeleteExpiresAsync(expireDate, _options.Value.DeleteBatchCount, stoppingToken); + } + } + + public override int Delay => _options.Value.CleaningExpireInterval; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs new file mode 100644 index 000000000..51cf8a6a0 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs @@ -0,0 +1,35 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class InfiniteLoopProcessor : ProcessorBase +{ + private readonly IProcessor _processor; + private readonly ILogger? _logger; + + public InfiniteLoopProcessor(IProcessor processor, ILogger? logger = null) + { + _processor = processor; + _logger = logger; + } + + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + await _processor.ExecuteAsync(stoppingToken); + await DelayAsync(((ProcessorBase)_processor).Delay); + } + catch (OperationCanceledException) + { + //ignore + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Processor '{ProcessorName}' failed", _processor.ToString()); + + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + } + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs new file mode 100644 index 000000000..c95c8d123 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs @@ -0,0 +1,19 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public abstract class ProcessorBase : IProcessor +{ + public abstract Task ExecuteAsync(CancellationToken stoppingToken); + + // /// + // /// Easy to switch between background tasks + // /// + /// unit: seconds + // /// + public Task DelayAsync(int delay) + => Task.Delay(TimeSpan.FromSeconds(delay)); + + /// + /// Task delay time, unit: seconds + /// + public virtual int Delay { get; } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs new file mode 100644 index 000000000..be120d71b --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs @@ -0,0 +1,76 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class RetryByDataProcessor : ProcessorBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptions _options; + private readonly IOptionsMonitor _appConfig; + private readonly ILogger? _logger; + + public RetryByDataProcessor( + IServiceProvider serviceProvider, + IOptionsMonitor appConfig, + IOptions options, + ILogger? logger = null) + { + _serviceProvider = serviceProvider; + _appConfig = appConfig; + _options = options; + _logger = logger; + } + + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var unitOfWork = scope.ServiceProvider.GetService(); + if (unitOfWork != null) + unitOfWork.UseTransaction = false; + + var dapr = _serviceProvider.GetRequiredService(); + var eventLogService = scope.ServiceProvider.GetRequiredService(); + + var retrieveEventLogs = + await eventLogService.RetrieveEventLogsFailedToPublishAsync(_options.Value.RetryBatchSize, _options.Value.MaxRetryTimes, _options.Value.MinimumRetryInterval); + + foreach (var eventLog in retrieveEventLogs) + { + try + { + if (LocalQueueProcessor.Default.IsExist(eventLog.EventId)) + continue; // The local queue is retrying, no need to retry + + await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId); + + _logger?.LogDebug("Publishing integration event {Event} to {PubsubName}.{TopicName}", eventLog, + _options.Value.PubSubName, + eventLog.Event.Topic); + + await dapr.PublishEventAsync(_options.Value.PubSubName, eventLog.Event.Topic, eventLog.Event, stoppingToken); + + LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); + + await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId); + } + catch (UserFriendlyException) + { + //Update state due to multitasking contention, no processing required + } + catch (Exception ex) + { + _logger?.LogError(ex, + "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", + eventLog.EventId, _appConfig.CurrentValue.AppId, eventLog); + await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); + } + finally + { + if (unitOfWork != null && unitOfWork.TransactionHasBegun) + await unitOfWork.CommitAsync(stoppingToken); + } + } + } + } + + public override int Delay => _options.Value.FailedRetryInterval; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs new file mode 100644 index 000000000..db7c9e540 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs @@ -0,0 +1,72 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; + +public class RetryByLocalQueueProcessor : ProcessorBase +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOptionsMonitor _appConfig; + private readonly IOptions _options; + private readonly ILogger? _logger; + + public RetryByLocalQueueProcessor( + IServiceProvider serviceProvider, + IOptionsMonitor appConfig, + IOptions options, + ILogger? logger = null) + { + _serviceProvider = serviceProvider; + _appConfig = appConfig; + _options = options; + _logger = logger; + } + + public override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var unitOfWork = scope.ServiceProvider.GetService(); + if (unitOfWork != null) + unitOfWork.UseTransaction = false; + + var dapr = _serviceProvider.GetRequiredService(); + var eventLogService = scope.ServiceProvider.GetRequiredService(); + + var retrieveEventLogs = LocalQueueProcessor.Default.RetrieveEventLogsFailedToPublishAsync(_options.Value.LocalRetryTimes, _options.Value.RetryBatchSize); + + foreach (var eventLog in retrieveEventLogs) + { + try + { + LocalQueueProcessor.Default.RetryJobs(eventLog.EventId); + + await eventLogService.MarkEventAsInProgressAsync(eventLog.EventId); + + _logger?.LogDebug( + "Publishing integration event {Event} to {PubsubName}.{TopicName}", + eventLog, + _options.Value.PubSubName, + eventLog.Topic); + + await dapr.PublishEventAsync(_options.Value.PubSubName, eventLog.Topic, eventLog.Event, stoppingToken); + + await eventLogService.MarkEventAsPublishedAsync(eventLog.EventId); + + LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); + } + catch (UserFriendlyException) + { + //Update state due to multitasking contention + LocalQueueProcessor.Default.RemoveJobs(eventLog.EventId); + } + catch (Exception ex) + { + _logger?.LogError(ex, + "Error Publishing integration event: {IntegrationEventId} from {AppId} - ({IntegrationEvent})", + eventLog.EventId, _appConfig.CurrentValue.AppId, eventLog); + await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); + } + } + } + } + + public override int Delay => _options.Value.LocalFailedRetryInterval; +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md new file mode 100644 index 000000000..00cd40f91 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.md @@ -0,0 +1,104 @@ +[ไธญ](README.zh-CN.md) | EN + +## IntegrationEventBus + +Example: + +```C# +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.Dapr //Send cross-process messages +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //Record cross-process message logs +Install-Package Masa.Contrib.Data.UoW.EF //Use UnitOfWork +``` + +1. Add IIntegrationEventBus + +```C# +builder.Services + .AddDaprEventBus(options=> + { + options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity")) + .UseEventLog(); + ) + }); +``` + +> CustomerDbContext needs to inherit IntegrationEventLogContext + +2. Custom IntegrationEvent + +```C# +public class DemoIntegrationEvent : IntegrationEvent +{ + public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name + + //todo Custom attribute parameters +} +``` + +3. Custom CustomDbContext + +```C# +public class CustomDbContext : IntegrationEventLogContext +{ + public DbSet Users { get; set; } = null!; + + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + + } +} +``` + +4. Send Event + +```C# +IIntegrationEventBus eventBus;//Get IIntegrationEventBus through DI +await eventBus.PublishAsync(new DemoIntegrationEvent());//Send cross-process events +``` + +5. Subscribe to events + +```C# +[Topic("pubsub", nameof(DomeIntegrationEvent))] +public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) +{ + //todo +} +``` + +### retry policy + +```C# +builder.Services + .AddDaprEventBus(options=> + { + // options.MaxRetryTimes = 50;//Maximum number of retries, default: 50 + // options.RetryBatchSize = 100;//Number of single retry events, used to get retry events from persistent data source, default 100 + // options.FailedRetryInterval = 60;//Persistent data source retry pause interval, default 60s + // options.CleaningExpireInterval = 300;//Clearing expired event pause interval, unit: s, default 300s + // options.ExpireDate = 24 * 3600;//Expiration time, CreationTime + ExpireDate = Expiration time, default 1 day + + // options.LocalFailedRetryInterval = 3;//Local queue retry pause interval, default 3s + // options.CleaningLocalQueueExpireInterval = 60;//Clearing local queue expired event pause interval, unit: s, default 60s + }); +``` + +Retry is divided into local queue retry and retry from persistent data source: + +local queue: + +Features: +- Short retry interval, support second-level retry interval +- Get data from memory, faster +- After the system crashes, the previous local queue will not be rebuilt, and will be automatically demoted to the persistent queue to retry the task + +Persistent data source queue: + +Features: + +- After the system crashes, the retry queue can be obtained from db or other persistent sources to ensure 100% retry of events +- As a downgrade solution for local memory queues, lower pressure on db or other data sources + +In the case of a single copy, the tasks of the two queues will only be executed in a single queue, and there will be no simultaneous execution of the two queues. +In the case of multiple copies, the same task may be executed by multiple copies. Although we have made idempotent, but the delivery guarantee is At Least Once, it is still possible that the event publishing is successful, but the state change fails. +At this point, the event may be re-sent. We recommend that the task executor retry across events. \ No newline at end of file diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md new file mode 100644 index 000000000..8705c78dc --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/README.zh-CN.md @@ -0,0 +1,105 @@ +ไธญ | [EN](README.md) + +## IntegrationEventBus + +็”จไพ‹๏ผš + +```C# +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.Dapr //ๅ‘้€่ทจ่ฟ›็จ‹ๆถˆๆฏ +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF //่ฎฐๅฝ•่ทจ่ฟ›็จ‹ๆถˆๆฏๆ—ฅๅฟ— +Install-Package Masa.Contrib.Data.UoW.EF //ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ +``` + +1. ๆทปๅŠ IIntegrationEventBus + +```C# +builder.Services + .AddDaprEventBus(options=> + { + options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity"))//ไฝฟ็”จๅทฅไฝœๅ•ๅ…ƒ๏ผŒๆŽจ่ไฝฟ็”จ + .UseEventLog(); + ) + }); +``` + +> CustomerDbContext ้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext + +2. ่‡ชๅฎšไน‰ IntegrationEvent + +```C# +public class DemoIntegrationEvent : IntegrationEvent +{ + public override string Topic { get; set; } = nameof(DemoIntegrationEvent);//dapr topic name + + //todo ่‡ชๅฎšไน‰ๅฑžๆ€งๅ‚ๆ•ฐ +} +``` + +3. ่‡ชๅฎšไน‰CustomDbContext + +```C# +public class CustomDbContext : IntegrationEventLogContext +{ + public DbSet Users { get; set; } = null!; + + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + + } +} +``` + +4. ๅ‘้€ Event + +```C# +IIntegrationEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIIntegrationEventBus +await eventBus.PublishAsync(new DemoIntegrationEvent());//ๅ‘้€่ทจ่ฟ›็จ‹ไบ‹ไปถ +``` + +5. ่ฎข้˜…ไบ‹ไปถ + +```C# +[Topic("pubsub", nameof(DomeIntegrationEvent))] +public async Task DomeIntegrationEventHandleAsync(DomeIntegrationEvent @event) +{ + //todo +} +``` + +### ้‡่ฏ•็ญ–็•ฅ + +```C# +builder.Services + .AddDaprEventBus(options=> + { + // options.MaxRetryTimes = 50;//ๆœ€ๅคง้‡่ฏ•ๆฌกๆ•ฐ, ้ป˜่ฎค๏ผš50 + // options.RetryBatchSize = 100;//ๅ•ๆฌก้‡่ฏ•ไบ‹ไปถๆ•ฐ้‡, ็”จไบŽไปŽๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ่Žทๅ–ๅพ…้‡่ฏ•ไบ‹ไปถ, ้ป˜่ฎค100 + // options.FailedRetryInterval = 60;//ๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้‡่ฏ•ๅœๆญ‡้—ด้š”, ้ป˜่ฎค60s + // options.CleaningExpireInterval = 300;//ๆธ…้™คๅทฒ่ฟ‡ๆœŸไบ‹ไปถๅœๆญ‡้—ด้š”๏ผŒๅ•ไฝ๏ผšs, ้ป˜่ฎค 300s + // options.ExpireDate = 24 * 3600;//่ฟ‡ๆœŸๆ—ถ้—ด๏ผŒCreationTime + ExpireDate = ่ฟ‡ๆœŸๆ—ถ้—ด, ้ป˜่ฎค1ๅคฉ + + // options.LocalFailedRetryInterval = 3;//ๆœฌๅœฐ้˜Ÿๅˆ—้‡่ฏ•ๅœๆญ‡้—ด้š”, ้ป˜่ฎค3s + // options.CleaningLocalQueueExpireInterval = 60;//ๆธ…้™คๆœฌๅœฐ้˜Ÿๅˆ—ๅทฒ่ฟ‡ๆœŸไบ‹ไปถๅœๆญ‡้—ด้š”๏ผŒๅ•ไฝ๏ผšs, ้ป˜่ฎค 60s + }); +``` + +้‡่ฏ•ๅˆ†ไธบๆœฌๅœฐ้˜Ÿๅˆ—้‡่ฏ•ไปฅๅŠไปŽๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้‡่ฏ•ไธค็ง๏ผš + +ๆœฌๅœฐ้˜Ÿๅˆ—๏ผš + +็‰น็‚น๏ผš +- ้‡่ฏ•้—ด้š”็Ÿญ๏ผŒๆ”ฏๆŒ็ง’็บงๅˆซ้‡่ฏ•้—ด้š” +- ไปŽๅ†…ๅญ˜่Žทๅ–ๆ•ฐๆฎ๏ผŒ้€Ÿๅบฆๆ›ดๅฟซ +- ็ณป็ปŸๅดฉๆบƒๅŽ๏ผŒไน‹ๅ‰็š„ๆœฌๅœฐ้˜Ÿๅˆ—ไธไผš้‡ๅปบ๏ผŒ่‡ชๅŠจ้™็บงๅˆฐๆŒไน…ๅŒ–้˜Ÿๅˆ—ไธญ้‡่ฏ•ไปปๅŠก + +ๆŒไน…ๅŒ–ๆ•ฐๆฎๆบ้˜Ÿๅˆ—๏ผš + +็‰น็‚น๏ผš + +- ็ณป็ปŸๅดฉๆบƒๅŽ๏ผŒๅฏไปฅไปŽdbๆˆ–่€…ๅ…ถไป–ๆŒไน…ๅŒ–ๆบ่Žทๅ–้‡่ฏ•้˜Ÿๅˆ—๏ผŒ็กฎไฟไบ‹ไปถ100%้‡่ฏ• +- ไฝœไธบๆœฌๅœฐๅ†…ๅญ˜้˜Ÿๅˆ—็š„้™็บงๆ–นๆกˆ๏ผŒๅฏนdbๆˆ–่€…ๅ…ถไป–ๆ•ฐๆฎๆบๅŽ‹ๅŠ›ๆ›ดไฝŽ + +ๅœจๅ•ๅ‰ฏๆœฌๆƒ…ๅ†ตไธ‹๏ผŒไธค็ง้˜Ÿๅˆ—็š„ไปปๅŠกไป…ไผšๅœจๅ•ไธช้˜Ÿๅˆ—ไธญๆ‰ง่กŒ๏ผŒไธไผšๅญ˜ๅœจไธคไธช้˜Ÿๅˆ—ๅŒๆ—ถๆ‰ง่กŒ็š„ๆƒ…ๅ†ตใ€‚ +ๅœจๅคšๅ‰ฏๆœฌๆƒ…ๅ†ตไธ‹๏ผŒๅŒไธ€ไธชไปปๅŠกๅฏ่ƒฝไผš่ขซๅคšไธชๅ‰ฏๆœฌๆ‰€ๆ‰ง่กŒ๏ผŒ่™ฝ็„ถๆˆ‘ไปฌๆœ‰ๅšๅน‚็ญ‰๏ผŒไฝ†ไธบไบคไป˜ไฟ่ฏๆ˜ฏ At Least Once๏ผŒไป็„ถๆœ‰ๅฏ่ƒฝๅ‡บ็Žฐไบ‹ไปถๅ‘ๅธƒๆˆๅŠŸ๏ผŒไฝ†็Šถๆ€ๆ›ดๆ”นๅคฑ่ดฅ็š„ๆƒ…ๅ†ต๏ผŒ +ๆญคๆ—ถไบ‹ไปถๅฏ่ƒฝไผš้‡ๅ‘๏ผŒๆˆ‘ไปฌๅปบ่ฎฎไปปๅŠกๆ‰ง่กŒ่€…ๅšๅฅฝๅฏน่ทจไบ‹ไปถ็š„้‡่ฏ• + diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs new file mode 100644 index 000000000..31a53c037 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs @@ -0,0 +1,20 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; + +public class DefaultHostedService : IProcessingServer +{ + private readonly IEnumerable _processors; + private readonly ILogger? _logger; + + public DefaultHostedService(IEnumerable processors, ILogger? logger = null) + { + _processors = processors; + _logger = logger; + } + + public Task ExecuteAsync(CancellationToken stoppingToken) + { + var processorTasks = _processors.Select(processor => new InfiniteLoopProcessor(processor, _logger)) + .Select(process => process.ExecuteAsync(stoppingToken)); + return Task.WhenAll(processorTasks); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..b6f5d4cec --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs @@ -0,0 +1,53 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddDaprEventBus( + this IServiceCollection services, + Action? options = null) + where TIntegrationEventLogService : class, IIntegrationEventLogService + => services.TryAddDaprEventBus(null, options); + + internal static IServiceCollection TryAddDaprEventBus( + this IServiceCollection services, + Action? builder, + Action? options = null) + where TIntegrationEventLogService : class, IIntegrationEventLogService + { + if (services.Any(service => service.ImplementationType == typeof(IntegrationEventBusProvider))) + return services; + + services.AddSingleton(); + + var dispatcherOptions = new DispatcherOptions(services); + options?.Invoke(dispatcherOptions); + + if (dispatcherOptions.Assemblies.Length == 0) + dispatcherOptions.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + services.TryAddSingleton(typeof(IOptions), + serviceProvider => Microsoft.Extensions.Options.Options.Create(dispatcherOptions)); + + LocalQueueProcessor.SetLogger(services); + services.AddDaprClient(builder); + services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.TryAddSingleton(); + services.AddHostedService(); + if (services.All(service => service.ServiceType != typeof(IUnitOfWork))) + { + var logger = services.BuildServiceProvider().GetService>(); + logger?.LogWarning("UoW is not enabled, local messages will not be integrated"); + } + + return services; + } + + private class IntegrationEventBusProvider + { + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs new file mode 100644 index 000000000..f1ea095e4 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/_Imports.cs @@ -0,0 +1,19 @@ +global using Dapr.Client; +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Internal; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; +global using Masa.Utils.Models.Config; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using System.Collections.Concurrent; +global using System.Reflection; +global using System.Text.Json.Serialization; + diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..646bc74fc --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/DispatcherOptionsExtensions.cs @@ -0,0 +1,56 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF; + +public static class DispatcherOptionsExtensions +{ + /// + /// IntegrationEventLogContext is a separate database + /// + /// + /// Separately specify database configuration for IntegrationEventLogContext + /// + /// + public static IDispatcherOptions UseEventLog( + this IDispatcherOptions options, + Action optionsBuilder) + { + if (options.Services == null) + throw new ArgumentNullException(nameof(options.Services)); + + if (optionsBuilder == null) + throw new ArgumentNullException(nameof(optionsBuilder)); + + if (options.Services.Any(service => service.ImplementationType == typeof(EventLogProvider))) return options; + options.Services.AddSingleton(); + + options.Services.AddCustomMasaDbContext(optionsBuilder); + return options; + } + + /// + /// User database with IntegrationEventLogContext merge + /// User-defined DbContext need IntegrationEventLogContext inheritance + /// + /// + /// + /// + public static IDispatcherOptions UseEventLog( + this IDispatcherOptions options) where TDbContext : IntegrationEventLogContext + { + if (options.Services == null) + throw new ArgumentNullException(nameof(options.Services)); + + if (typeof(TDbContext) == typeof(IntegrationEventLogContext)) + throw new NotSupportedException( + $"{typeof(TDbContext).FullName} must be IntegrationEventLogContext derived classes, or using UseEventLog() replace UseEventLog<{typeof(TDbContext).FullName}>()"); + + if (options.Services.Any(service => service.ImplementationType == typeof(EventLogProvider))) return options; + options.Services.AddSingleton(); + + options.Services.TryAddScoped(serviceProvider => serviceProvider.GetRequiredService()); + return options; + } + + private class EventLogProvider + { + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs new file mode 100644 index 000000000..9eb919a83 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogContext.cs @@ -0,0 +1,53 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF; + +public class IntegrationEventLogContext : MasaDbContext +{ + public IntegrationEventLogContext( + MasaDbContextOptions? options = null, + MasaDbContextOptions? eventLogContext = null) + : base(eventLogContext ?? options ?? + throw new InvalidOperationException("Options extension of type 'CoreOptionsExtension' not found")) + { + } + + public DbSet EventLogs { get; set; } + + protected override void OnModelCreatingExecuting(ModelBuilder builder) + { + builder.Entity(ConfigureEventLogEntry); + } + + private void ConfigureEventLogEntry(EntityTypeBuilder builder) + { + builder.ToTable("IntegrationEventLog"); + + builder.HasKey(e => e.Id); + + builder.Property(e => e.Id) + .IsRequired(); + + builder.Property(e => e.Content) + .IsRequired(); + + builder.Property(e => e.CreationTime) + .IsRequired(); + + builder.Property(e => e.ModificationTime) + .IsRequired(); + + builder.Property(e => e.State) + .IsRequired(); + + builder.Property(e => e.TimesSent) + .IsRequired(); + + builder.Property(e => e.RowVersion) + .IsRowVersion(); + + builder.Property(e => e.EventTypeName) + .IsRequired(); + + builder.HasIndex(e => new { e.State, e.ModificationTime },"index_state_modificationtime"); + builder.HasIndex(e => new { e.State, e.TimesSent, e.ModificationTime },"index_state_timessent_modificationtime"); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs new file mode 100644 index 000000000..cf238b9b6 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/IntegrationEventLogService.cs @@ -0,0 +1,158 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF; + +public class IntegrationEventLogService : IIntegrationEventLogService +{ + private readonly IntegrationEventLogContext _eventLogContext; + private readonly IServiceProvider _serviceProvider; + private readonly Logger? _logger; + private IEnumerable? _eventTypes; + + public IntegrationEventLogService( + IntegrationEventLogContext eventLogContext, + IServiceProvider serviceProvider, + Logger? logger = null) + { + _eventLogContext = eventLogContext; + _serviceProvider = serviceProvider; + _logger = logger; + } + + /// + /// Get messages to retry + /// + /// maximum number of retries per retry + /// + /// default: 60s + /// + public async Task> RetrieveEventLogsFailedToPublishAsync(int retryBatchSize = 200, int maxRetryTimes = 10, int minimumRetryInterval = 60) + { + //todo: Subsequent acquisition of the current time needs to be uniformly replaced with the unified time method provided by the framework, which is convenient for subsequent uniform replacement to UTC time or other urban time. The default setting here is Utc time. + var time = DateTime.UtcNow.AddSeconds(-minimumRetryInterval); + var result = await _eventLogContext.EventLogs + .Where(e => (e.State == IntegrationEventStates.PublishedFailed || e.State == IntegrationEventStates.InProgress) && + e.TimesSent <= maxRetryTimes && + e.ModificationTime < time) + .OrderBy(o => o.CreationTime) + .Take(retryBatchSize) + .ToListAsync(); + + if (result.Any()) + { + _eventTypes ??= _serviceProvider.GetRequiredService().GetAllEventTypes() + .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type)); + + return result.OrderBy(o => o.CreationTime) + .Select(e => e.DeserializeJsonContent(_eventTypes.First(t => t.Name == e.EventTypeShortName))); + } + + return result; + } + + public async Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) + { + if (transaction == null) + throw new ArgumentNullException(nameof(transaction)); + + if (_eventLogContext.Database.CurrentTransaction == null) + await _eventLogContext.Database.UseTransactionAsync(transaction, Guid.NewGuid()); + + var eventLogEntry = new IntegrationEventLog(@event, _eventLogContext.Database.CurrentTransaction!.TransactionId); + await _eventLogContext.EventLogs.AddAsync(eventLogEntry); + await _eventLogContext.SaveChangesAsync(); + + CheckAndDetached(eventLogEntry); + } + + public Task MarkEventAsPublishedAsync(Guid eventId) + { + return UpdateEventStatus(eventId, IntegrationEventStates.Published, eventLog => + { + if (eventLog.State != IntegrationEventStates.InProgress) + { + _logger?.LogWarning( + "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", + IntegrationEventStates.Published, eventLog.State, eventLog.Id); + throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.Published}, the current State is {eventLog.State}, Id: {eventLog.Id}"); + } + }); + } + + public Task MarkEventAsInProgressAsync(Guid eventId) + { + return UpdateEventStatus(eventId, IntegrationEventStates.InProgress, eventLog => + { + if (eventLog.State != IntegrationEventStates.NotPublished && eventLog.State != IntegrationEventStates.PublishedFailed) + { + _logger?.LogWarning( + "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", + IntegrationEventStates.InProgress, eventLog.State, eventLog.Id); + throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.InProgress}, the current State is {eventLog.State}, Id: {eventLog.Id}"); + } + }); + } + + public Task MarkEventAsFailedAsync(Guid eventId) + { + return UpdateEventStatus(eventId, IntegrationEventStates.PublishedFailed, eventLog => + { + if (eventLog.State != IntegrationEventStates.InProgress) + { + _logger?.LogWarning( + "Failed to modify the state of the local message table to {OptState}, the current State is {State}, Id: {Id}", + IntegrationEventStates.PublishedFailed, eventLog.State, eventLog.Id); + throw new UserFriendlyException($"Failed to modify the state of the local message table to {IntegrationEventStates.PublishedFailed}, the current State is {eventLog.State}, Id: {eventLog.Id}"); + } + }); + } + + public async Task DeleteExpiresAsync(DateTime expiresAt, int batchCount = 1000, CancellationToken token = default) + { + var eventLogs = _eventLogContext.EventLogs.Where(e => e.ModificationTime < expiresAt && e.State == IntegrationEventStates.Published) + .OrderBy(e => e.CreationTime).Take(batchCount); + _eventLogContext.EventLogs.RemoveRange(eventLogs); + await _eventLogContext.SaveChangesAsync(token); + + if (_eventLogContext.ChangeTracker.QueryTrackingBehavior != QueryTrackingBehavior.TrackAll) + { + foreach (var log in eventLogs) + { + _eventLogContext.Entry(log).State = EntityState.Detached; + } + } + } + + private async Task UpdateEventStatus(Guid eventId, IntegrationEventStates status, Action? action = null) + { + var eventLogEntry = _eventLogContext.EventLogs.FirstOrDefault(e => e.EventId == eventId); + if (eventLogEntry == null) + throw new ArgumentException(nameof(eventId)); + + action?.Invoke(eventLogEntry); + + + eventLogEntry.State = status; + eventLogEntry.ModificationTime = eventLogEntry.GetCurrentTime(); + if (status == IntegrationEventStates.InProgress) + eventLogEntry.TimesSent++; + + try + { + _eventLogContext.EventLogs.Update(eventLogEntry); + await _eventLogContext.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException ex) + { + throw new UserFriendlyException(ex.Message); + } + + CheckAndDetached(eventLogEntry); + } + + private void CheckAndDetached(IntegrationEventLog integrationEvent) + { + if (_eventLogContext.ChangeTracker.QueryTrackingBehavior != QueryTrackingBehavior.TrackAll) + { + _eventLogContext.Entry(integrationEvent).State = EntityState.Detached; + } + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs new file mode 100644 index 000000000..713b3d3a9 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/DbContextExtensions.cs @@ -0,0 +1,27 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; + +internal static class DbContextExtensions +{ + internal static IServiceCollection AddCustomMasaDbContext( + this IServiceCollection services, + Action contextAction) + where TDbContext : MasaDbContext + { + var optionsBuilder = new DbContextOptionsBuilder(); + contextAction.Invoke(optionsBuilder); + + services.AddDbContext(); + services.TryAddScoped(typeof(MasaDbContextOptions), serviceProvider => + { + return CreateMasaDbContextOptions(optionsBuilder.Options); + }); + return services; + } + + private static MasaDbContextOptions CreateMasaDbContextOptions( + DbContextOptions originOptions) + where TDbContext : MasaDbContext + { + return new MasaDbContextOptions(originOptions, new List(), new List()); + } +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs new file mode 100644 index 000000000..16cb5013a --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/QueryFilterProvider.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; + +internal abstract class QueryFilterProvider : IQueryFilterProvider +{ + public abstract LambdaExpression OnExecuting(IMutableEntityType entityType); +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs new file mode 100644 index 000000000..c06013b21 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Internal/SaveChangesFilter.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; + +internal abstract class SaveChangesFilter : ISaveChangesFilter +{ + public abstract void OnExecuting(ChangeTracker changeTracker); +} diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj new file mode 100644 index 000000000..898584399 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.csproj @@ -0,0 +1,20 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md new file mode 100644 index 000000000..48d5c8461 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.md @@ -0,0 +1,24 @@ +[ไธญ](README.zh-CN.md) | EN + +## Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF + +> Provide support for sending IntegrationEvent + +Example๏ผš + +```C# +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF +``` + +1. Add EventLogs.EF + +```C# +.AddDaprEventBus(options => +{ + options + // TODO + .UseEventLog(); +} +``` + +> Tip: CustomDbContext needs to inherit IntegrationEventLogContext \ No newline at end of file diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md new file mode 100644 index 000000000..a67b975a5 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/README.zh-CN.md @@ -0,0 +1,24 @@ +ไธญ | [EN](README.md) + +## Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF + +> ไธบๅ‘้€IntegrationEventๆไพ›ๆ”ฏๆŒ + +็”จไพ‹๏ผš + +```C# +Install-Package Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF +``` + +1. ไฝฟ็”จEventLogs.EF + +```C# +.AddDaprEventBus(options => +{ + options + // TODO + .UseEventLog(); +} +``` + +> ๆ็คบ๏ผšCustomDbContext้œ€่ฆ็ปงๆ‰ฟIntegrationEventLogContext diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs new file mode 100644 index 000000000..0ebedfeb8 --- /dev/null +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF/_Imports.cs @@ -0,0 +1,18 @@ +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; +global using Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Internal; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Microsoft.EntityFrameworkCore.Metadata; +global using Microsoft.EntityFrameworkCore.Metadata.Builders; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using System; +global using System.Collections.Generic; +global using System.Data.Common; +global using System.Linq; +global using System.Linq.Expressions; +global using System.Threading.Tasks; diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/Command.cs b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/Command.cs new file mode 100644 index 000000000..320a8fe9d --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/Command.cs @@ -0,0 +1,15 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Commands; + +public record Command(Guid Id, DateTime CreationTime) : ICommand +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } + + public Command() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/CommandHandler.cs b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/CommandHandler.cs new file mode 100644 index 000000000..6360d7e17 --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Commands/CommandHandler.cs @@ -0,0 +1,12 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Commands; + +public abstract class CommandHandler : ICommandHandler, ISagaEventHandler + where TCommand : ICommand +{ + public abstract Task HandleAsync(TCommand @event); + + public virtual Task CancelAsync(TCommand @event) + { + return Task.CompletedTask; + } +} diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs.csproj b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs.csproj new file mode 100644 index 000000000..e7b7fd034 --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs.csproj @@ -0,0 +1,19 @@ +๏ปฟ + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/Query.cs b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/Query.cs new file mode 100644 index 000000000..cf8a3a24d --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/Query.cs @@ -0,0 +1,15 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Queries; + +public abstract record Query(Guid Id, DateTime CreationTime) : IQuery + where TResult : notnull +{ + [JsonIgnore] + public Guid Id { get; } = Id; + + [JsonIgnore] + public DateTime CreationTime { get; } = CreationTime; + + public abstract TResult Result { get; set; } + + public Query() : this(Guid.NewGuid(), DateTime.UtcNow) { } +} diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/QueryHandler.cs b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/QueryHandler.cs new file mode 100644 index 000000000..fa577d51f --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/Queries/QueryHandler.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Queries; + +public abstract class QueryHandler : IQueryHandler + where TQuery : IQuery + where TResult : notnull +{ + public abstract Task HandleAsync(TQuery @event); +} diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.md b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.md new file mode 100644 index 000000000..313d5daa5 --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.md @@ -0,0 +1,88 @@ +[ไธญ](README.zh-CN.md) | EN + +## Cqrs + +Example๏ผš + +```C# +1. Define Command and Query base classes +2. Support the Handler of Saga mode, and provide the basic implementation of CommandHandler +``` + +```C# +Install-Package Masa.Contrib.ReadWriteSpliting.Cqrs +``` + +##### Query๏ผš + +1. Define Query + +```C# +public class CatalogItemQuery : Query> +{ + public string Name { get; set; } = default!; + + public override List Result { get; set; } = default!; +} +``` + +2. Define QueryHandler + +```C# +public class CatalogQueryHandler : QueryHandler> +{ + private readonly ICatalogItemRepository _catalogItemRepository; + + public CatalogQueryHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; + + public async Task HandleAsync(CatalogItemQuery query) + { + query.Result = await _catalogItemRepository.GetListAsync(query.Name); + } +} +``` + +3. Send Query + +```c# +IEventBus eventBus;//Get IEventBus through DI +await eventBus.PublishAsync(new CatalogItemQuery() { Name = "Rolex" }); +``` + +> Tip: The generic type after Query is consistent with the return type of Result. You need to assign a value to Result in Handler so that the caller can get the result. + +##### Command + +1. Define Command + +```c# +public class CreateCatalogItemCommand : Command +{ + public string Name { get; set; } = default!; + + //todo +} +``` + +2. Add CommandHandler + +```c# +public class CatalogCommandHandler : CommandHandler +{ + private readonly ICatalogItemRepository _catalogItemRepository; + + public CatalogCommandHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; + + public async Task HandleAsync(CreateCatalogItemCommand command) + { + //todo + } +} +``` + +3. Send Command + +```C# +IEventBus eventBus;//Get IEventBus through DI +await eventBus.PublishAsync(new CreateCatalogItemCommand()); +``` \ No newline at end of file diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.zh-CN.md b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.zh-CN.md new file mode 100644 index 000000000..60203537e --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.zh-CN.md @@ -0,0 +1,88 @@ +ไธญ | [EN](README.md) + +## Cqrs + +็”จไพ‹๏ผš + +```C# +1. ๅฎšไน‰ไบ†CommandไธŽQueryๅŸบ็ฑป +2. ๆ”ฏๆŒSagaๆจกๅผ็š„Handler๏ผŒๅนถๆไพ›CommandHandlerๅŸบ็ก€ๅฎž็Žฐ +``` + +```C# +Install-Package Masa.Contrib.ReadWriteSpliting.Cqrs +``` + +##### Query๏ผš + +1. ๅฎšไน‰Query + +```C# +public class CatalogItemQuery : Query> +{ + public string Name { get; set; } = default!; + + public override List Result { get; set; } = default!; +} +``` + +2. ๅฎšไน‰QueryHandler + +```C# +public class CatalogQueryHandler : QueryHandler> +{ + private readonly ICatalogItemRepository _catalogItemRepository; + + public CatalogQueryHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; + + public async Task HandleAsync(CatalogItemQuery query) + { + query.Result = await _catalogItemRepository.GetListAsync(query.Name); + } +} +``` + +3. ๅ‘้€Query + +```c# +IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus +await eventBus.PublishAsync(new CatalogItemQuery() { Name = "Rolex" }); +``` + +> ๆ็คบ๏ผšQueryๅŽ็š„ๆณ›ๅž‹ไธŽResult็š„่ฟ”ๅ›ž็ฑปๅž‹ไฟๆŒไธ€่‡ด๏ผŒ้œ€่ฆๅ†HandlerไธญไธบResult่ต‹ๅ€ผ๏ผŒไปฅไพฟ่ฐƒ็”จๆ–นๅพ—ๅˆฐ็ป“ๆžœ + +##### Command + +1. ๅฎšไน‰ Command + +```c# +public class CreateCatalogItemCommand : Command +{ + public string Name { get; set; } = default!; + + //todo +} +``` + +2. ๆทปๅŠ  CommandHandler + +```c# +public class CatalogCommandHandler : CommandHandler +{ + private readonly ICatalogItemRepository _catalogItemRepository; + + public CatalogCommandHandler(ICatalogItemRepository catalogItemRepository) => _catalogItemRepository = catalogItemRepository; + + public async Task HandleAsync(CreateCatalogItemCommand command) + { + //todo + } +} +``` + +3. ๅ‘้€ Command + +```C# +IEventBus eventBus;//้€š่ฟ‡DIๅพ—ๅˆฐIEventBus +await eventBus.PublishAsync(new CreateCatalogItemCommand()); +``` diff --git a/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/_Imports.cs b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/_Imports.cs new file mode 100644 index 000000000..f06ffc6c4 --- /dev/null +++ b/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/_Imports.cs @@ -0,0 +1,6 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.ReadWriteSpliting.Cqrs.Commands; +global using Masa.BuildingBlocks.ReadWriteSpliting.Cqrs.Queries; +global using System.Text.Json.Serialization; + diff --git a/src/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj b/src/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj new file mode 100644 index 000000000..380a5a44b --- /dev/null +++ b/src/Service/Masa.Contrib.Service.MinimalAPIs/Masa.Contrib.Service.MinimalAPIs.csproj @@ -0,0 +1,18 @@ +๏ปฟ + + + net6.0 + enable + + + + + + + + + + + + + diff --git a/src/Service/Masa.Contrib.Service.MinimalAPIs/README.md b/src/Service/Masa.Contrib.Service.MinimalAPIs/README.md new file mode 100644 index 000000000..54a68bbbb --- /dev/null +++ b/src/Service/Masa.Contrib.Service.MinimalAPIs/README.md @@ -0,0 +1,53 @@ +[ไธญ](README.zh-CN.md) | EN + +## MinimalAPI + +Original usage๏ผš + +```C# +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); +app.MapGet("/api/v1/helloworld", ()=>"Hello World"); +app.Run(); +``` + +Example๏ผš + +```c# +Install-Package Masa.Contrib.Service.MinimalAPIs +``` + +1. Add MinimalAPI + +```c# +var builder = WebApplication.CreateBuilder(args); +var app = builder.Services + .AddServices(builder); +``` + +2. Customize Service and inherit ServiceBase + +```c# +public class IntegrationEventService : ServiceBase +{ + public IntegrationEventService(IServiceCollection services) : base(services) + { + App.MapGet("/api/v1/payment/HelloWorld", HelloWorld); + } + + public string HelloWorld() + { + return "Hello World"; + } +} +``` + +> Tip: The service that inherits ServiceBase is registered in singleton mode, if you need to obtain it from DI + +```C# +public async Task DeleteBasketByIdAsync(string id, [FromServices] IBasketRepository repository) +{ + await repository.DeleteBasketAsync(id); +} +``` + diff --git a/src/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md b/src/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md new file mode 100644 index 000000000..294d0cab0 --- /dev/null +++ b/src/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md @@ -0,0 +1,53 @@ +ไธญ | [EN](README.md) + +## MinimalAPI + +ๅŽŸๅง‹็”จๆณ•๏ผš + +```C# +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); +app.MapGet("/api/v1/helloworld", ()=>"Hello World"); +app.Run(); +``` + +็”จไพ‹๏ผš + +```c# +Install-Package Masa.Contrib.Service.MinimalAPIs +``` + +1. ๆทปๅŠ MinimalAPI + +```c# +var builder = WebApplication.CreateBuilder(args); +var app = builder.Services + .AddServices(builder); +``` + +2. ่‡ชๅฎšไน‰Serviceๅนถ็ปงๆ‰ฟServiceBase๏ผŒๅฆ‚๏ผš + +```c# +public class IntegrationEventService : ServiceBase +{ + public IntegrationEventService(IServiceCollection services) : base(services) + { + App.MapGet("/api/v1/payment/HelloWorld", HelloWorld); + } + + public string HelloWorld() + { + return "Hello World"; + } +} +``` + +> ๆ็คบ๏ผš็ปงๆ‰ฟServiceBase็š„ๆœๅŠกไธบๅ•ไพ‹ๆจกๅผๆณจๅ†Œ๏ผŒๅฆ‚ๆžœ้œ€่ฆไปŽDI่Žทๅ–่Žทๅ– + +```C# +public async Task DeleteBasketByIdAsync(string id, [FromServices] IBasketRepository repository) +{ + await repository.DeleteBasketAsync(id); +} +``` + diff --git a/src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs b/src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs new file mode 100644 index 000000000..dab3dda27 --- /dev/null +++ b/src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceBase.cs @@ -0,0 +1,119 @@ +namespace Masa.Contrib.Service.MinimalAPIs; + +public abstract class ServiceBase : IService +{ + private ServiceProvider _serviceProvider = default!; + + public WebApplication App => _serviceProvider.GetRequiredService(); + + public string BaseUri { get; } + + public IServiceCollection Services { get; protected set; } + + public ServiceBase(IServiceCollection services) + { + Services = services; + _serviceProvider = services.BuildServiceProvider(); + } + + public ServiceBase(IServiceCollection services, string baseUri) + { + BaseUri = baseUri; + Services = services; + _serviceProvider = services.BuildServiceProvider(); + } + + public TService? GetService() => _serviceProvider.GetService(); + + public TService GetRequiredService() + where TService : notnull + => Services.BuildServiceProvider().GetRequiredService(); + + #region Map GET,POST,PUT,DELETE + + /// + /// Adds a to the that matches HTTP GET requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapGet(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapGet(pattern, handler); + } + + /// + /// Adds a to the that matches HTTP POST requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapPost(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapPost(pattern, handler); + } + + /// + /// Adds a to the that matches HTTP PUT requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapPut(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapPut(pattern, handler); + } + + /// + /// Adds a to the that matches HTTP DELETE requests + /// for the specified pattern, a combination of and or name. + /// + /// The delegate executed when the endpoint is matched. It's name is a part of pattern if is null. + /// The custom uri. It is a part of pattern if it is not null. + /// Determines whether to remove the string 'Async' at the end. + /// A that can be used to further customize the endpoint. + protected RouteHandlerBuilder MapDelete(Delegate handler, string? customUri = null, bool trimEndAsync = true) + { + customUri ??= FormatAction(handler.Method.Name, trimEndAsync); + + var pattern = CombineUris(BaseUri, customUri); + + return App.MapDelete(pattern, handler); + } + + private static string FormatAction(string action, bool trimEndAsync) + { + if (trimEndAsync && action.EndsWith("Async")) + { + var i = action.LastIndexOf("Async", StringComparison.Ordinal); + action = action[..i]; + } + + return action; + } + + private static string CombineUris(params string[] uris) + { + return string.Join("/", uris.Select(u => u.Trim('/'))); + } + + #endregion +} diff --git a/src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs b/src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..38d5d9571 --- /dev/null +++ b/src/Service/Masa.Contrib.Service.MinimalAPIs/ServiceCollectionExtensions.cs @@ -0,0 +1,42 @@ +namespace Masa.Contrib.Service.MinimalAPIs; + +public static class ServiceCollectionExtensions +{ + /// + /// Add all classes that inherit from ServiceBase to Microsoft.Extensions.DependencyInjection.IServiceCollection + /// Notice: this method must be last call. + /// + /// The Microsoft.AspNetCore.Builder.WebApplicationBuilder. + /// + public static WebApplication AddServices(this WebApplicationBuilder builder) + => builder.Services.AddServices(builder); + + /// + /// Add all classes that inherit from ServiceBase to Microsoft.Extensions.DependencyInjection.IServiceCollection + /// Notice: this method must be last call. + /// + /// The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service to. + /// The Microsoft.AspNetCore.Builder.WebApplicationBuilder. + /// + public static WebApplication AddServices(this IServiceCollection services, WebApplicationBuilder builder) + { + if (services.All(service => service.ImplementationType != typeof(MinimalApisMarkerService))) + { + services.AddSingleton(); + services.TryAddScoped(sp => services); + + services.AddSingleton(new Lazy(() => builder.Build(), LazyThreadSafetyMode.ExecutionAndPublication)) + .AddTransient(serviceProvider => serviceProvider.GetRequiredService>().Value); + + services.AddServices(true, AppDomain.CurrentDomain.GetAssemblies()); + } + + var serviceProvider = services.BuildServiceProvider(); + return serviceProvider.GetRequiredService(); + } + + private class MinimalApisMarkerService + { + + } +} diff --git a/src/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs b/src/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs new file mode 100644 index 000000000..93bb8ef77 --- /dev/null +++ b/src/Service/Masa.Contrib.Service.MinimalAPIs/_Imports.cs @@ -0,0 +1,8 @@ +global using Masa.BuildingBlocks.Service.MinimalAPIs; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Routing; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using System; +global using System.Linq; +global using System.Threading; diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs new file mode 100644 index 000000000..5dfc056e7 --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccClientTest.cs @@ -0,0 +1,388 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests; + +[TestClass] +public class DccClientTest +{ + private Mock _client; + private IServiceCollection _services; + private IServiceProvider _serviceProvider => _services.BuildServiceProvider(); + private JsonSerializerOptions _jsonSerializerOptions; + private DccSectionOptions _dccSectionOptions; + private CustomTrigger _trigger; + + [TestInitialize] + public void Initialize() + { + _client = new Mock(); + _services = new ServiceCollection(); + _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + _dccSectionOptions = new DccSectionOptions() + { + Environment = "Test", + Cluster = "Default", + AppId = "DccTest", + ConfigObjects = new List() + { + "Test1" + }, + Secret = "" + }; + _trigger = new CustomTrigger(_jsonSerializerOptions); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsync(string environment, string cluster, string appId, string configObject) + { + Action valueChanged = delegate (string? val) { }; + _client.Setup(client => client.GetAsync(It.IsAny(), valueChanged).Result).Returns(() => null).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, valueChanged), "configObject invalid" + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => "test").Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => "{}").Verifiable(); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), "configObject invalid" + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new + { + ConfigFormat = "1", + Content = "" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + await Assert.ThrowsExceptionAsync(async () + => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), "configObject invalid" + ); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease + { + ConfigFormat = (ConfigFormats)5, + Content = "" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + await Assert.ThrowsExceptionAsync( + async () => await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()), + "Unsupported configuration type"); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsyncByJson(string environment, string cluster, string appId, string configObject) + { + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + + var brand = new Brands("Apple"); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Raw == brand.Serialize(_jsonSerializerOptions)); + Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Json); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsyncByText(string environment, string cluster, string appId, string configObject) + { + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = "test" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Raw == "test"); + Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Text); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestGetRawAsyncByProperty(string environment, string cluster, string appId, string configObject) + { + List properties = new List() + { + new() + { + Key = "Brand", + Value = "Microsoft" + } + }; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = properties.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Raw == properties.Serialize(_jsonSerializerOptions)); + Assert.IsTrue(ret.ConfigurationType == ConfigurationTypes.Text); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetAsyncByJson(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + var newBrand = new Brands("Microsoft2"); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetAsync(environment, cluster, appId, configObject, (Brands br) => + { + Assert.IsTrue(br.Id == newBrand.Id); + Assert.IsTrue(br.Name == newBrand.Name); + }); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Serialize(_jsonSerializerOptions).Equals(brand.Serialize(_jsonSerializerOptions))); + _trigger.Execute(); + + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id == newBrand.Id && ret.Name == newBrand.Name); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + newBrand.Name = "Masa"; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id && ret.Name == brand.Name); + _trigger.Execute(); + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsTrue(ret.Id == newBrand.Id && ret.Name == "Masa"); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id && ret.Name == brand.Name); + + Initialize(); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => + { + await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + }); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetAsyncByText(string environment, string cluster, string appId, string configObject) + { + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = "test" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => + { + await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + }); + + Initialize(); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = "1" + }.Serialize(_jsonSerializerOptions)).Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + Assert.IsTrue(await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()) == 1); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetAsyncByProperty(string environment, string cluster, string appId, string configObject) + { + var brand = new List() + { + new() + { + Key = "Id", + Value = Guid.NewGuid().ToString(), + }, + new() + { + Key = "Name", + Value = "Microsoft" + } + }; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Properties, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id.ToString() == brand.Where(b => b.Key == "Id").Select(t => t.Value).FirstOrDefault() && + ret.Name == brand.Where(b => b.Key == "Name").Select(t => t.Value).FirstOrDefault()); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetDynamicAsyncByJson(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + var newBrand = new Brands("Microsoft2"); + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, (dynamic obj) => + { + Assert.IsTrue((obj.Id + "") == newBrand.Id.ToString()); + + Assert.IsTrue(obj.Name == newBrand.Name); + }); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id == brand.Id.ToString()); + + Assert.IsTrue(ret.Name == brand.Name); + + _trigger.Execute(); + + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == newBrand.Id.ToString() && ret.Name == newBrand.Name); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Callback((string str, Action action) => + { + _trigger.Formats = ConfigFormats.Json; + _trigger.Content = newBrand.Serialize(_jsonSerializerOptions); + _trigger.Action = action; + }).Verifiable(); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id.ToString()); + Assert.IsTrue(ret.Name == brand.Name); + _trigger.Execute(); + + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == newBrand.Id.ToString() && ret.Name == newBrand.Name); + + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Json, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)); + client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + Assert.IsTrue(ret.Id == brand.Id.ToString() && ret.Name == brand.Name); + } + + [TestMethod] + [DataRow("DccOptions.ManageServiceAddress", "http://localhost:6379")] + [DataRow("DccOptions.RedisOptions.DefaultDatabase", "0")] + [DataRow("DccOptions.RedisOptions.Password", "")] + public async Task GetDynamicAsync(string key, string value) + { + var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build(); + _services.AddSingleton(configuration); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var res = (await client.GetDynamicAsync(key)); + Assert.IsTrue(res + "" == value); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetDynamicAsyncByText(string environment, string cluster, string appId, string configObject) + { + string result = "Test"; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Text, + Content = result + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => + { + await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + }); + } + + [TestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task GetDynamicAsyncByProperty(string environment, string cluster, string appId, string configObject) + { + var brand = new List() + { + new() + { + Key = "Id", + Value = Guid.NewGuid().ToString(), + }, + new() + { + Key = "Name", + Value = "Microsoft" + } + }; + _client.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result).Returns(() => new PublishRelease() + { + ConfigFormat = ConfigFormats.Properties, + Content = brand.Serialize(_jsonSerializerOptions) + }.Serialize(_jsonSerializerOptions)).Verifiable(); + var client = new ConfigurationApiClient(_serviceProvider, _client.Object, _jsonSerializerOptions, _dccSectionOptions, null); + var ret = await client.GetDynamicAsync(environment, cluster, appId, configObject, It.IsAny>()); + Assert.IsNotNull(ret); + + Assert.IsTrue(ret.Id == brand.Where(b => b.Key == "Id").Select(b => b.Value).FirstOrDefault()); + Assert.IsTrue(ret.Name == brand.Where(b => b.Key == "Name").Select(b => b.Value).FirstOrDefault()); + } +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs new file mode 100644 index 000000000..3d387ff25 --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs @@ -0,0 +1,162 @@ +using Masa.Contrib.BasicAbility.Dcc.Internal; +using Masa.Utils.Caller.Core; +using System.Net; + +namespace Masa.Contrib.BasicAbility.Dcc.Tests; + +[TestClass] +public class DccManageTest +{ + private DccSectionOptions _dccSectionOptions; + private JsonSerializerOptions _jsonSerializerOptions; + private Mock _callerProvider; + + [TestInitialize] + public void Initialize() + { + _dccSectionOptions = new DccSectionOptions() + { + Environment = "Test", + Cluster = "Default", + AppId = "DccTest", + ConfigObjects = new List() + { + "Test1" + }, + Secret = "Secret" + }; + _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + _callerProvider = new Mock(); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestUpdateAsync(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(brand.Serialize(_jsonSerializerOptions)) + }).Verifiable(); + + var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); + await manage.UpdateAsync(environment, cluster, appId, configObject, brand); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestUpdateAsyncAndError(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + + _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() + { + StatusCode = HttpStatusCode.ExpectationFailed, + Content = new StringContent("error") + }).Verifiable(); + + var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => await manage.UpdateAsync(environment, cluster, appId, configObject, brand)); + } + + [DataTestMethod] + [DataRow("Test", "Default", "DccTest", "Brand")] + public async Task TestUpdateAsyncAndCustomError(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + _callerProvider.Setup(factory => factory.PutAsync(It.IsAny(), It.IsAny(), default).Result).Returns(() => new HttpResponseMessage() + { + StatusCode = (HttpStatusCode)299, + Content = new StringContent("custom error") + }).Verifiable(); + + var manage = new ConfigurationApiManage(_callerProvider.Object, _dccSectionOptions, null); + await Assert.ThrowsExceptionAsync(async () => await manage.UpdateAsync(environment, cluster, appId, configObject, brand)); + } + + [DataTestMethod] + [DataRow("DccTest", "Secret")] + [DataRow("DccTest2", "Secret2")] + [DataRow("DccTest3", "")] + public void TestGetSecret(string appId, string secret) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, new List() + { + new() + { + Environment = "Test2", + Cluster = "Default2", + AppId = "DccTest2", + ConfigObjects = new List() + { + "Test12" + }, + Secret = "Secret2" + } + }); + if (string.IsNullOrEmpty(secret)) + Assert.ThrowsException(() => api.GetSecret(appId)); + else + Assert.IsTrue(api.GetSecret(appId) == secret); + } + + [DataTestMethod] + [DataRow("Test2", "Test2")] + [DataRow("", "Test")] + public void TestGetEnvironment(string environment, string outEnvironment) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + Assert.IsTrue(api.GetEnvironment(environment) == outEnvironment); + } + + [DataTestMethod] + [DataRow("CustomCluster", "CustomCluster")] + [DataRow("", "Default")] + public void GetCluster(string cluster, string outCluster) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + Assert.IsTrue(api.GetCluster(cluster) == outCluster); + } + + [DataTestMethod] + [DataRow("CustomAppid", "CustomAppid")] + [DataRow("", "DccTest")] + public void GetAppid(string appId, string outAppid) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + Assert.IsTrue(api.GetAppId(appId) == outAppid); + } + + [DataTestMethod] + [DataRow("configObject", "configObject")] + [DataRow("", "")] + public void GetConfigObject(string configObject, string outConfigObject) + { + var api = new CustomConfigurationAPI(_dccSectionOptions, null); + if (string.IsNullOrEmpty(configObject)) + Assert.ThrowsException(() => api.GetConfigObject(configObject)); + else + Assert.IsTrue(api.GetConfigObject(configObject) == outConfigObject); + } +} + +public class CustomConfigurationAPI : ConfigurationAPIBase +{ + public CustomConfigurationAPI(DccSectionOptions defaultSectionOption, List? expandSectionOptions) : base(defaultSectionOption, expandSectionOptions) + { + } + + public new string GetSecret(string appId) => base.GetSecret(appId); + + public new string GetEnvironment(string environment) => base.GetEnvironment(environment); + + public new string GetCluster(string cluster) => base.GetCluster(cluster); + + public new string GetAppId(string appId) => base.GetAppId(appId); + + public new string GetConfigObject(string configObject) => base.GetConfigObject(configObject); +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs new file mode 100644 index 000000000..9c257a4fc --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs @@ -0,0 +1,799 @@ +using Masa.Utils.Caching.Core.Interfaces; +using Masa.Utils.Caching.Core.Models; +using Masa.Utils.Caching.DistributedMemory.Models; +using Masa.Utils.Caller.Core; +using Masa.Utils.Caller.HttpClient; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace Masa.Contrib.BasicAbility.Dcc.Tests; + +[TestClass] +public class DccTest +{ + private string DEFAULT_CLIENT_NAME = "masa.plugins.caching.dcc"; + private Mock _masaConfigurationBuilder; + private JsonSerializerOptions _jsonSerializerOptions; + private IServiceCollection _services; + + private Mock _memoryCacheClientFactory; + private Mock _memoryCache; + private Mock _distributedCacheClient; + private const string DefaultEnvironmentName = "ASPNETCORE_ENVIRONMENT"; + private const string DEFAULT_SUBSCRIBE_KEY_PREFIX = "masa.dcc:"; + + [TestInitialize] + public void Initialize() + { + _masaConfigurationBuilder = new Mock(); + _memoryCacheClientFactory = new Mock(); + _memoryCache = new Mock(); + _distributedCacheClient = new Mock(); + _services = new ServiceCollection(); + _jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }; + } + + [TestMethod] + public void TestErrorDccSection() + { + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary()).Verifiable(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(new ServiceCollection())); + } + + [TestMethod] + public void TestTryAddConfigurationApiClient() + { + _memoryCacheClientFactory.Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)).Returns(() => null!).Verifiable(); + _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), null!); + Assert.IsTrue(_services.Count(service => service.ServiceType == typeof(IConfigurationApiClient) && service.Lifetime == ServiceLifetime.Singleton) == 1); + Assert.ThrowsException(() => + { + var clienties = _services.BuildServiceProvider().GetServices(); + }); + + _services = new ServiceCollection(); + _memoryCacheClientFactory + .Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)) + .Returns(() => new MemoryCacheClient(_memoryCache.Object, _distributedCacheClient.Object, SubscribeKeyTypes.ValueTypeFullNameAndKey)) + .Verifiable(); + _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), new JsonSerializerOptions() + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + + var clienties = _services.BuildServiceProvider().GetServices(); + Assert.IsTrue(clienties.Count() == 1); + + _services = new ServiceCollection(); + _memoryCacheClientFactory + .Setup(factory => factory.CreateClient(DEFAULT_CLIENT_NAME)) + .Returns(() => new MemoryCacheClient(_memoryCache.Object, _distributedCacheClient.Object, Utils.Caching.Core.Models.SubscribeKeyTypes.ValueTypeFullNameAndKey)) + .Verifiable(); + _services.AddSingleton(serviceProvider => _memoryCacheClientFactory.Object); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), _jsonSerializerOptions); + MasaConfigurationExtensions.TryAddConfigurationApiClient(_services, new DccSectionOptions(), new List(), _jsonSerializerOptions); + clienties = _services.BuildServiceProvider().GetServices(); + Assert.IsTrue(clienties.Count() == 1); + } + + [TestMethod] + public void TestTryAddConfigurationApiManage() + { + Mock httpClientFactory = new(); + _services.AddSingleton(httpClientFactory.Object); + _services.AddCaller(options => options.UseHttpClient()); + + MasaConfigurationExtensions.TryAddConfigurationApiManage(_services, new DccSectionOptions(), new List()); + MasaConfigurationExtensions.TryAddConfigurationApiManage(_services, new DccSectionOptions(), new List()); + Assert.IsTrue(_services.Count(service => service.ServiceType == typeof(IConfigurationApiManage) && service.Lifetime == ServiceLifetime.Singleton) == 1); + var serviceProvider = _services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseDCCAndErrorSection() + { + _services.AddCaller(options => options.UseHttpClient()); + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary()).Verifiable(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, "", null, null), "configureOptions"); + } + + [TestMethod] + public void TestUseDCCAndNullDccConfigurationOption() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => null!, option => + { + option.AppId = "Test"; + option.Environment = "Test"; + option.ConfigObjects = new List() { "Te" }; + }, null), "configureOptions"); + } + + [TestMethod] + public void TestCustomCaller() + { + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = string.Empty, + ConfigFormat = ConfigFormats.Text + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + _masaConfigurationBuilder.Object.UseDcc(_services, () => new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }, option => + { + option.AppId = "Test"; + option.Environment = "Test"; + option.ConfigObjects = new List() + { + "Settings" + }; + }, null, jsonSerializerOption => + { + jsonSerializerOption.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + }, option => + { + option.UseHttpClient(builder => + { + builder.Name = "CustomHttpClient"; + builder.Configure = opt => opt.BaseAddress = new Uri("https://github.com"); + }); + }); + var callerProvider = _services.BuildServiceProvider().GetRequiredService().CreateClient("CustomHttpClient"); + Assert.IsNotNull(callerProvider); + } + + [TestMethod] + public void TestUseDCCAndEmptyDccServiceAddress() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "", + }; + }, null!, null), "DccServiceAddress"); + } + + [TestMethod] + public void TestUseDCCAndErrorDccService() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = null! + } + }; + }, null!, null), "Servers"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions + { + Servers = new List() + } + }; + }, null!, null), "Servers"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions + { + Servers = new List() + { + new() + { + Host="", + Port=8080 + } + } + } + }; + }, null!, null), "Servers"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host="localhost", + Port=-1 + } + } + } + }; + }, null!, null), "Servers"); + } + + [TestMethod] + public void TestUseDCCAndErrorDefaultSectionOption() + { + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, null!, null), "defaultSectionOptions"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = ""; + }, null), "AppId cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = null!; + }, null), "ConfigObjects cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List(); + }, null), "ConfigObjects cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, null), "Error getting environment information, please make sure the value of ASPNETCORE_ENVIRONMENT has been configured"); + } + + [TestMethod] + public void TestUseDCCAndErrorExpansionSectionOptions() + { + System.Environment.SetEnvironmentVariable(DefaultEnvironmentName, "Test"); + + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test2", + } + }; + }), "ConfigObjects in the extension section cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test2", + ConfigObjects=new List() + } + }; + }), "ConfigObjects in the extension section cannot be empty"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test", + ConfigObjects=new List() + { + "Settings" + } + } + }; + }), "The current section already exists, no need to mount repeatedly"); + + _services = new ServiceCollection(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, option => + { + option.ExpandSections = new List() + { + new() + { + AppId = "Test2", + ConfigObjects=new List() + { + "Settings" + } + }, + new() + { + AppId = "Test2", + ConfigObjects=new List() + { + "Settings" + } + } + }; + }), "The current section already exists, no need to mount repeatedly"); + } + + [DataTestMethod] + [DataRow("Development", "Default", "WebApplication1", "Brand")] + public void TestUseDCCAndSuccess(string environment, string cluster, string appId, string configObject) + { + System.Environment.SetEnvironmentVariable(DefaultEnvironmentName, "Test"); + var brand = new Brands("Microsoft"); + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = System.Text.Json.JsonSerializer.Serialize(brand), + ConfigFormat = ConfigFormats.Json + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + _masaConfigurationBuilder.Object.UseDcc(_services, () => + { + return new DccConfigurationOptions() + { + ManageServiceAddress = "https://github.com", + RedisOptions = new Utils.Caching.Redis.Models.RedisConfigurationOptions() + { + Servers = new List() + { + new() + { + Host = "localhost", + Port = 6379 + } + } + } + }; + }, option => + { + option.AppId = "Test"; + option.ConfigObjects = new List() + { + "Brand" + }; + }, null); + var optionFactory = _services.BuildServiceProvider().GetRequiredService>(); + var option = optionFactory.Create(DEFAULT_CLIENT_NAME); + + Assert.IsTrue(option.SubscribeKeyType == SubscribeKeyTypes.SpecificPrefix); + + Assert.IsTrue(option.SubscribeKeyPrefix == DEFAULT_SUBSCRIBE_KEY_PREFIX); + } + + [DataTestMethod] + [DataRow("Development", "Default", "WebApplication1", "Brand")] + public void TestUseDccAndSingleSection(string environment, string cluster, string appId, string configObject) + { + CustomTrigger trigger = new CustomTrigger(_jsonSerializerOptions); + var brand = new Brands("Microsoft"); + var newBrand = new Brands("Masa"); + + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = brand.Serialize(_jsonSerializerOptions), + ConfigFormat = ConfigFormats.Text + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue( + configurationApiClient + .GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) + .GetAwaiter() + .GetResult().Raw == brand.Serialize(_jsonSerializerOptions)); + trigger.Execute(); + + Initialize(); + + Dictionary masaDic = new Dictionary() + { + { "Id", Guid.NewGuid().ToString() }, + { "Name", "Masa" } + }; + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = masaDic.Serialize(_jsonSerializerOptions), + ConfigFormat = ConfigFormats.Json + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).Result.Raw == masaDic.Serialize(_jsonSerializerOptions)); + + Initialize(); + + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = "Test", + ConfigFormat = ConfigFormats.Text + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).GetAwaiter().GetResult().Raw == "Test"); + + Initialize(); + + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = null, + ConfigFormat = ConfigFormats.Text + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + Assert.IsTrue(configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()).GetAwaiter().GetResult().Raw == null); + + Initialize(); + + response = JsonSerializer.Serialize(new PublishRelease() + { + Content = "Test", + ConfigFormat = (ConfigFormats)4 + }); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + Assert.ThrowsException(() => _masaConfigurationBuilder.Object.UseDcc(_services)); + } + + [TestMethod] + public void TestUseDccAndExpandSections() + { + var brand = new Brands("Microsoft"); + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = JsonSerializer.Serialize(brand), + ConfigFormat = ConfigFormats.Json + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("expandSections.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services); + + var result = configurationApiClient.GetRawAsync("Test", "Default", "DccTest", "Test1", It.IsAny>()) + .ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.IsTrue(result.Raw == JsonSerializer.Serialize(brand)); + } + + [DataTestMethod] + [DataRow("Development", "Default", "WebApplication1", "Brand")] + public void TestUseMultiDcc(string environment, string cluster, string appId, string configObject) + { + var brand = new Brands("Microsoft"); + var response = JsonSerializer.Serialize(new PublishRelease() + { + Content = JsonSerializer.Serialize(brand), + ConfigFormat = ConfigFormats.Json + }); + Mock memoryCacheClient = new(); + memoryCacheClient.Setup(client => client.GetAsync(It.IsAny(), It.IsAny>()).Result) + .Returns(() => response); + + var configurationApiClient = new ConfigurationApiClient(_services.BuildServiceProvider(), + memoryCacheClient.Object, _jsonSerializerOptions, new Mock().Object, new List()); + _services.AddSingleton(configurationApiClient); + + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true); + + _masaConfigurationBuilder.Setup(builder => builder.GetSectionRelations()).Returns(new Dictionary() + { + { "Appsettings",chainedConfiguration.Build() } + }).Verifiable(); + + _masaConfigurationBuilder.Object.UseDcc(_services).UseDcc(_services); + var result = configurationApiClient.GetRawAsync(environment, cluster, appId, configObject, It.IsAny>()) + .ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.IsTrue(result.Raw == JsonSerializer.Serialize(brand)); + + var httpClient = _services.BuildServiceProvider().GetRequiredService().CreateClient(DEFAULT_CLIENT_NAME); + Assert.IsTrue(httpClient.BaseAddress!.ToString() == "http://localhost:6379/"); + } + +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs new file mode 100644 index 000000000..d4eeec1ae --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Common/SerializeCommon.cs @@ -0,0 +1,7 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Common; + +internal static class SerializeCommon +{ + public static string Serialize(this object obj, JsonSerializerOptions? jsonSerializerOptions) + => JsonSerializer.Serialize(obj, jsonSerializerOptions); +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs new file mode 100644 index 000000000..9a3b9b6dc --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/Property.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Config; + +internal class Property +{ + public string Key { get; set; } = default!; + + public string Value { get; set; } = default!; +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs new file mode 100644 index 000000000..3da0d158a --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Config/PublishRelease.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests.Internal; + +internal class PublishRelease +{ + public ConfigFormats ConfigFormat { get; set; } + + public string? Content { get; set; } +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs new file mode 100644 index 000000000..32162b75d --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/CustomTrigger.cs @@ -0,0 +1,26 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests.Internal; + +public class CustomTrigger +{ + private JsonSerializerOptions _jsonSerializerOptions; + + public CustomTrigger(JsonSerializerOptions jsonSerializerOptions) + { + _jsonSerializerOptions = jsonSerializerOptions; + } + + internal ConfigFormats Formats { get; set; } + + internal string Content { get; set; } + + internal Action Action { get; set; } + + internal void Execute() + { + Action?.Invoke(new PublishRelease() + { + ConfigFormat = Formats, + Content = Content + }.Serialize(_jsonSerializerOptions)); + } +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs new file mode 100644 index 000000000..0bb0207c7 --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Enum/ConfigFormats.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; + +internal enum ConfigFormats +{ + Properties = 1, + Text, + Json +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs new file mode 100644 index 000000000..c9d7e06bd --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Internal/Model/Brands.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Model; + +internal class Brands +{ + public Guid Id { get; init; } + + public string Name { get; set; } + + public Brands() + => this.Id = Guid.NewGuid(); + + public Brands(string Name) : this() + => this.Name = Name; +} diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/Masa.Contrib.BasicAbility.Dcc.Tests.csproj b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Masa.Contrib.BasicAbility.Dcc.Tests.csproj new file mode 100644 index 000000000..eee86d67a --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/Masa.Contrib.BasicAbility.Dcc.Tests.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + enable + false + enable + + + + + Always + + + Always + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs new file mode 100644 index 000000000..70394c661 --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs @@ -0,0 +1,14 @@ +global using Masa.BuildingBlocks.Configuration; +global using Masa.Contrib.BasicAbility.Dcc.Options; +global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal; +global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Common; +global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Config; +global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; +global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Model; +global using Masa.Utils.Caching.DistributedMemory; +global using Masa.Utils.Caching.DistributedMemory.Interfaces; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System.Text.Json; diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/appsettings.json b/test/Masa.Contrib.BasicAbility.Dcc.Tests/appsettings.json new file mode 100644 index 000000000..144652eea --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/appsettings.json @@ -0,0 +1,23 @@ +{ + "DccOptions": { + "ManageServiceAddress": "http://localhost:6379", + "SubscribeKeyPrefix": "masa.dcc:", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8888 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "WebApplication1", + "Environment": "Development", + "Cluster": "Default", + "ConfigObjects": [ + "Brand" + ], + "Sectet": "" +} \ No newline at end of file diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/expandSections.json b/test/Masa.Contrib.BasicAbility.Dcc.Tests/expandSections.json new file mode 100644 index 000000000..28e953152 --- /dev/null +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/expandSections.json @@ -0,0 +1,32 @@ +{ + "DccOptions": { + "ManageServiceAddress": "http://localhost:6379", + "RedisOptions": { + "Servers": [ + { + "Host": "localhost", + "Port": 8888 + } + ], + "DefaultDatabase": 0, + "Password": "" + } + }, + "AppId": "DccTest", + "Environment": "Test", + "Cluster": "Default", + "ConfigObjects": [ + "Test1" + ], + "Sectet": "", + "ExpandSections": [ + { + "AppId": "DccTest2", + "Environment": "Test2", + "Cluster": "Default", + "ConfigObjects": [ + "Test3" + ] + } + ] +} \ No newline at end of file diff --git a/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs new file mode 100644 index 000000000..1b71a28a5 --- /dev/null +++ b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/ErrorKafkaOptions.cs @@ -0,0 +1,12 @@ +namespace Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests; + +public class ErrorKafkaOptions : KafkaOptions +{ + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + public ErrorKafkaOptions() + { + base.Section = "KafkaOptions"; + } +} diff --git a/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs new file mode 100644 index 000000000..c95944e07 --- /dev/null +++ b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/KafkaOptions.cs @@ -0,0 +1,15 @@ +namespace Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests; + +public class KafkaOptions : MasaConfigurationOptions +{ + public string Servers { get; set; } + + public int ConnectionPoolSize { get; set; } + + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + public KafkaOptions() + { + base.ParentSection = ""; + } +} diff --git a/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj new file mode 100644 index 000000000..3f9de168b --- /dev/null +++ b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs new file mode 100644 index 000000000..d22e47c4d --- /dev/null +++ b/test/Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using Masa.BuildingBlocks.Configuration; +global using System.Text.Json.Serialization; diff --git a/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj b/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj new file mode 100644 index 000000000..3f9de168b --- /dev/null +++ b/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs b/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs new file mode 100644 index 000000000..3d7cc45b3 --- /dev/null +++ b/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/MountSectionRedisOptions.cs @@ -0,0 +1,12 @@ +๏ปฟnamespace Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests; + +public class MountSectionRedisOptions : MasaConfigurationOptions +{ + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = null; + + public override SectionTypes SectionType { get; init; } = SectionTypes.ConfigurationAPI; +} diff --git a/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs b/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs new file mode 100644 index 000000000..d22e47c4d --- /dev/null +++ b/test/Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using Masa.BuildingBlocks.Configuration; +global using System.Text.Json.Serialization; diff --git a/test/Masa.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs b/test/Masa.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs new file mode 100644 index 000000000..0fdb617ff --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/Config/RabbitMqOptions.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Configuration.Tests.Config; + +public class RabbitMqOptions : MasaConfigurationOptions +{ + public string HostName { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public string VirtualHost { get; set; } + + public string Port { get; set; } + + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; +} diff --git a/test/Masa.Contrib.Configuration.Tests/Config/RedisOptions.cs b/test/Masa.Contrib.Configuration.Tests/Config/RedisOptions.cs new file mode 100644 index 000000000..db4fc07cb --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/Config/RedisOptions.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Configuration.Tests.Config; + +public class RedisOptions +{ + public string Ip { get; set; } + + public string Password { get; set; } + + public int Port { get; set; } +} diff --git a/test/Masa.Contrib.Configuration.Tests/Config/SystemOptions.cs b/test/Masa.Contrib.Configuration.Tests/Config/SystemOptions.cs new file mode 100644 index 000000000..c037bbb94 --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/Config/SystemOptions.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Configuration.Tests.Config; + +public class SystemOptions : MasaConfigurationOptions +{ + [JsonIgnore] + public override string? ParentSection { get; init; } = "Appsettings"; + + [JsonIgnore] + public override string? Section { get; init; } = null; + + public override SectionTypes SectionType { get; init; } = SectionTypes.Local; + + public string? Name { get; set; } +} diff --git a/test/Masa.Contrib.Configuration.Tests/ConfigurationTest.cs b/test/Masa.Contrib.Configuration.Tests/ConfigurationTest.cs new file mode 100644 index 000000000..1e150f0cd --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/ConfigurationTest.cs @@ -0,0 +1,266 @@ +namespace Masa.Contrib.Configuration.Tests; + +[TestClass] +public class ConfigurationTest +{ + private IConfigurationBuilder _configurationBuilder; + + [TestInitialize] + public void Initialize() + { + _configurationBuilder = new ConfigurationBuilder(); + } + + [TestMethod] + public void TestAddSection() + { + var masaConfigurationBuilder = new MasaConfigurationBuilder(_configurationBuilder); + Assert.ThrowsException(() => masaConfigurationBuilder.AddSection(null!)); + + Assert.ThrowsException(() => masaConfigurationBuilder.AddSection(new ConfigurationBuilder())); + + masaConfigurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true), "appsettings" + ); + + Assert.IsTrue(masaConfigurationBuilder.GetSectionRelations().Count == 1); + + masaConfigurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("redis.json", true, true) + ); + Assert.IsTrue(masaConfigurationBuilder.GetSectionRelations().Count == 2); + + Assert.ThrowsException(() => + { + masaConfigurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("rabbitMq.json", true, true) + ); + }); + } + + [TestMethod] + public void TestAddCustomSection() + { + var builder = WebApplication.CreateBuilder(); + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true), "RedisOptions"); + + configurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + + configurationBuilder.UseMasaOptions(option => + { + option.Mapping(SectionTypes.Local, ""); + }); + }); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var redisOption = serviceProvider.GetRequiredService>(); + + Assert.IsNotNull(configuration); + Assert.IsNotNull(redisOption); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + } + + [TestMethod] + public void TestAddMasaConfiguration() + { + var builder = WebApplication.CreateBuilder(); + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + option.Mapping(SectionTypes.Local, "", "") + ); + }); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var redisOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:Ip"] == "localhost"); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + + var rabbitMqOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:RabbitMqOptions:UserName"] == "admin"); + Assert.IsTrue(rabbitMqOption.Value.UserName == "admin" && rabbitMqOption.Value.Password == "admin"); + } + + [TestMethod] + public void TestAddMultiMasaConfiguration() + { + var builder = WebApplication.CreateBuilder(); + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + option.Mapping(SectionTypes.Local, "", "") + ); + }).AddMasaConfiguration(); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var redisOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:Ip"] == "localhost"); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + + var rabbitMqOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(configuration["Local:RabbitMqOptions:UserName"] == "admin"); + Assert.IsTrue(rabbitMqOption.Value.UserName == "admin" && rabbitMqOption.Value.Password == "admin"); + } + + [TestMethod] + public void TestAutoMapSectionError() + { + var builder = WebApplication.CreateBuilder(); + builder.Host.ConfigureAppConfiguration((context, config) => { config.Sources.Clear(); }); + var chainedConfiguration = new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true); + builder.Configuration.AddConfiguration(chainedConfiguration.Build()); + + Assert.ThrowsException(() => + builder.AddMasaConfiguration(configurationBuilder => + { + }, "Appsettings", typeof(ConfigurationTest).Assembly, typeof(KafkaOptions).Assembly)); + } + + [TestMethod] + public void TestAutoMapAndErrorSection() + { + var builder = WebApplication.CreateBuilder(); + Assert.ThrowsException(() => + { + return builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); //Mount to the Local section + }, "Appsettings", typeof(ConfigurationTest).Assembly, typeof(MountSectionRedisOptions).Assembly); + }); + } + + [TestMethod] + public void TestRepeatMappting() + { + var builder = WebApplication.CreateBuilder(); + Assert.ThrowsException(() => + { + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + { + option.Mapping(SectionTypes.Local, "", ""); + option.Mapping(SectionTypes.Local, "", ""); + }); + }); + }); + } + + [TestMethod] + public void TestCreateMasaConfiguration() + { + var services = new ServiceCollection(); + services.CreateMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("redis.json", true, true) + ); + configurationBuilder.AddSection( + new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + configurationBuilder.UseMasaOptions(option => + option.Mapping(SectionTypes.Local, "", "") + ); + }, new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true), "Appsettings"); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var redisOption = serviceProvider.GetRequiredService>(); + Assert.IsTrue(redisOption.Value.Ip == "localhost"); + } + + [TestMethod] + public void TestNullSection() + { + var services = new ServiceCollection(); + var ex = Assert.ThrowsException(() => services.CreateMasaConfiguration(null)); + Assert.IsTrue(ex.Message == "Please add the section to be loaded"); + } + + [TestMethod] + public void TestConfigurationChange() + { + var builder = WebApplication.CreateBuilder(); + + var rootPath = builder.Environment.ContentRootPath; + builder.AddMasaConfiguration(configurationBuilder => + { + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(rootPath) + .AddJsonFile("redis.json", true, true), "RedisOptions"); + + configurationBuilder.AddSection(new ConfigurationBuilder() + .SetBasePath(rootPath) + .AddJsonFile("rabbitMq.json", true, true), "RabbitMqOptions" + ); + + configurationBuilder.UseMasaOptions(option => + { + option.Mapping(SectionTypes.Local, ""); + }); + }, "Appsettings", typeof(ConfigurationTest).Assembly); + var serviceProvider = builder.Services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + var systemOption = serviceProvider.GetRequiredService>(); + + Assert.IsNotNull(configuration); + Assert.IsNotNull(systemOption); + Assert.IsTrue(systemOption.Value.Name == "Masa TEST"); + + var newRedisOption = systemOption.Value; + newRedisOption.Name = null; + + File.WriteAllText(Path.Combine(rootPath, "appsettings.json"), System.Text.Json.JsonSerializer.Serialize(new { SystemOptions = newRedisOption })); + + Thread.Sleep(2000); + var option = serviceProvider.GetRequiredService>(); + Assert.IsTrue(option.CurrentValue.Name == ""); + } +} diff --git a/test/Masa.Contrib.Configuration.Tests/Masa.Contrib.Configuration.Tests.csproj b/test/Masa.Contrib.Configuration.Tests/Masa.Contrib.Configuration.Tests.csproj new file mode 100644 index 000000000..6e73e97db --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/Masa.Contrib.Configuration.Tests.csproj @@ -0,0 +1,43 @@ + + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + Always + + + + Always + + + + Always + + + + diff --git a/test/Masa.Contrib.Configuration.Tests/_Imports.cs b/test/Masa.Contrib.Configuration.Tests/_Imports.cs new file mode 100644 index 000000000..bffad49c1 --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/_Imports.cs @@ -0,0 +1,10 @@ +global using Masa.BuildingBlocks.Configuration; +global using Masa.Contrib.Configuration.ErrorSectionAutoMap.Tests; +global using Masa.Contrib.Configuration.MountErrorSectionAutoMap.Tests; +global using Masa.Contrib.Configuration.Tests.Config; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System.Text.Json.Serialization; diff --git a/test/Masa.Contrib.Configuration.Tests/appsettings.json b/test/Masa.Contrib.Configuration.Tests/appsettings.json new file mode 100644 index 000000000..4ea30efd8 --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/appsettings.json @@ -0,0 +1,9 @@ +{ + "KafkaOptions": { + "Servers": "Kafka Server", + "int": 10 + }, + "SystemOptions": { + "Name": "Masa TEST" + } +} \ No newline at end of file diff --git a/test/Masa.Contrib.Configuration.Tests/rabbitMq.json b/test/Masa.Contrib.Configuration.Tests/rabbitMq.json new file mode 100644 index 000000000..cbff2c19a --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/rabbitMq.json @@ -0,0 +1,7 @@ +{ + "HostName": "localhost", + "UserName": "admin", + "Password": "admin", + "VirtualHost": "/", + "Port": 5672 +} \ No newline at end of file diff --git a/test/Masa.Contrib.Configuration.Tests/redis.json b/test/Masa.Contrib.Configuration.Tests/redis.json new file mode 100644 index 000000000..ebce8f626 --- /dev/null +++ b/test/Masa.Contrib.Configuration.Tests/redis.json @@ -0,0 +1,5 @@ +{ + "Ip": "localhost", + "Password": "", + "Port": 6379 +} \ No newline at end of file diff --git a/test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs b/test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs new file mode 100644 index 000000000..e3c0e638c --- /dev/null +++ b/test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Courses.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Data.Contracts.EF.Tests.Domain.Entities; + +public class Courses : AggregateRoot +{ + public Courses() + { + Id = Guid.NewGuid(); + IsDeleted = false; + } + + public string Name { get; set; } + + public bool IsDeleted { get; set; } +} diff --git a/test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs b/test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs new file mode 100644 index 000000000..2bd6b8ebc --- /dev/null +++ b/test/Masa.Contrib.Data.Contracts.EF.Tests/Domain/Entities/Students.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Data.Contracts.EF.Tests.Domain.Entities; + +public class Students : AuditAggregateRoot +{ + public Students() + { + Id = Guid.NewGuid(); + RegisterTime = DateTime.UtcNow; + } + + public string Name { get; set; } + + public int Age { get; set; } + + public DateTime RegisterTime { get; private set; } +} diff --git a/test/Masa.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs b/test/Masa.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs new file mode 100644 index 000000000..d69d0d73e --- /dev/null +++ b/test/Masa.Contrib.Data.Contracts.EF.Tests/Infrastructure/CustomDbContext.cs @@ -0,0 +1,12 @@ +using Masa.Contrib.Data.Contracts.EF.Tests.Domain.Entities; + +namespace Masa.Contrib.Data.Contracts.EF.Tests.Infrastructure; + +public class CustomDbContext : MasaDbContext +{ + public DbSet Students { get; set; } + + public DbSet Courses { get; set; } + + public CustomDbContext(MasaDbContextOptions options) : base(options) { } +} diff --git a/test/Masa.Contrib.Data.Contracts.EF.Tests/Masa.Contrib.Data.Contracts.EF.Tests.csproj b/test/Masa.Contrib.Data.Contracts.EF.Tests/Masa.Contrib.Data.Contracts.EF.Tests.csproj new file mode 100644 index 000000000..216097dca --- /dev/null +++ b/test/Masa.Contrib.Data.Contracts.EF.Tests/Masa.Contrib.Data.Contracts.EF.Tests.csproj @@ -0,0 +1,28 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs b/test/Masa.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs new file mode 100644 index 000000000..43b6a345f --- /dev/null +++ b/test/Masa.Contrib.Data.Contracts.EF.Tests/SoftDeleteTest.cs @@ -0,0 +1,117 @@ +using Masa.Contrib.Data.Contracts.EF.Tests.Domain.Entities; +using Masa.Contrib.Data.Contracts.EF.Tests.Infrastructure; + +namespace Masa.Contrib.Data.Contracts.EF.Tests; + +[TestClass] +public class SoftDeleteTest : IDisposable +{ + protected readonly SqliteConnection _connection; + + public SoftDeleteTest() + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + } + + public void Dispose() + { + _connection.Close(); + } + + [TestMethod] + public void UseNotUseUoW() + { + var services = new ServiceCollection(); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + Assert.ThrowsException(() => option.UseSoftDelete(services), "Please add UoW first."); + }); + } + + [TestMethod] + public void TestUseSoftDelete() + { + Mock uoW = new(); + uoW.Setup(u => u.Transaction).Verifiable(); + var services = new ServiceCollection(); + services.AddScoped(serviceProvider => uoW.Object); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + option.UseSoftDelete(services); + }); + + var serviceProvider = services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + dbContext.Set().Add(new Students() + { + Name = "Tom", + Age = 18 + }); + dbContext.SaveChanges(); + Assert.IsTrue(dbContext.Students.Count() == 1); + uoW.Verify(u => u.Transaction, Times.Never); + + var student = dbContext.Students.FirstOrDefault(s => s.Name == "Tom"); + Assert.IsNotNull(student); + dbContext.Set().Remove(student); + dbContext.SaveChanges(); + + Assert.IsTrue(!dbContext.Students.Any()); + + student.IsDeleted = false; + dbContext.SaveChanges(); + Assert.IsTrue(dbContext.Students.Count() == 1); + + uoW = new(); + uoW.Setup(u => u.Transaction).Verifiable(); + uoW.Setup(u => u.UseTransaction).Returns(() => true); + services = new ServiceCollection(); + services.AddScoped(serviceProvider => uoW.Object); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + option.UseSoftDelete(services); + }); + + serviceProvider = services.BuildServiceProvider(); + dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + dbContext.Set().Add(new Courses() + { + Name = "Chinese" + }); + dbContext.SaveChanges(); + Assert.IsTrue(dbContext.Courses.Count() == 1); + uoW.Verify(u => u.Transaction, Times.Never); + + var course = dbContext.Set().FirstOrDefault(c => c.Name == "Chinese"); + Assert.IsNotNull(course); + dbContext.Set().Remove(course); + dbContext.SaveChanges(); + Assert.IsTrue(!dbContext.Courses.Any()); + + course.IsDeleted = false; + dbContext.SaveChanges(); + Assert.IsTrue(!dbContext.Courses.Any()); + } + + [TestMethod] + public void TestUseMultiSoftDelete() + { + Mock uoW = new(); + uoW.Setup(u => u.Transaction).Verifiable(); + var services = new ServiceCollection(); + services.AddScoped(serviceProvider => uoW.Object); + services.AddMasaDbContext(option => + { + option.UseSqlite(_connection); + option.UseSoftDelete(services).UseSoftDelete(services); + }); + } +} diff --git a/test/Masa.Contrib.Data.Contracts.EF.Tests/_Imports.cs b/test/Masa.Contrib.Data.Contracts.EF.Tests/_Imports.cs new file mode 100644 index 000000000..2df7da23c --- /dev/null +++ b/test/Masa.Contrib.Data.Contracts.EF.Tests/_Imports.cs @@ -0,0 +1,10 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Ddd.Domain.Entities; +global using Masa.BuildingBlocks.Ddd.Domain.Entities.Auditing; +global using Masa.Contrib.Data.Contracts.EF.Tests.Domain.Entities; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs new file mode 100644 index 000000000..c8bef2b09 --- /dev/null +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs @@ -0,0 +1,44 @@ +namespace Masa.Contrib.Data.UoW.EF.Tests; + +public class CustomerDbContext : MasaDbContext +{ + public CustomerDbContext() + { + + } + + public CustomerDbContext(MasaDbContextOptions options) : base(options) { } + + public DbSet User { get; set; } + + protected override void OnModelCreatingExecuting(ModelBuilder builder) + { + builder.Entity(ConfigureUserEntry); + } + + void ConfigureUserEntry(EntityTypeBuilder builder) + { + builder.ToTable("Users"); + + builder.HasKey(e => e.Id); + + builder.Property(e => e.Id) + .IsRequired(); + + builder.Property(e => e.Name) + .HasMaxLength(6) + .IsRequired(); + } +} + +public class Users +{ + public Guid Id { get; private set; } + + public string Name { get; set; } = default!; + + public Users() + { + this.Id = Guid.NewGuid(); + } +} diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj new file mode 100644 index 000000000..a1cbe734a --- /dev/null +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestBase.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestBase.cs new file mode 100644 index 000000000..ba64be781 --- /dev/null +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestBase.cs @@ -0,0 +1,17 @@ +namespace Masa.Contrib.Data.UoW.EF.Tests; + +public class TestBase : IDisposable +{ + protected readonly SqliteConnection _connection; + + protected TestBase() + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + } + + public void Dispose() + { + _connection.Close(); + } +} diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs new file mode 100644 index 000000000..73c2ba130 --- /dev/null +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -0,0 +1,171 @@ +namespace Masa.Contrib.Data.UoW.EF.Tests; + +[TestClass] +public class TestUnitOfWork : TestBase +{ + private Mock _options; + + [TestInitialize] + public void Initialize() + { + _options = new(); + _options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); + } + + [TestMethod] + public void TestAddUoWAndNullServices() + { + var options = new Mock(); + Assert.ThrowsException(() => options.Object.UseUoW()); + } + + [TestMethod] + public void TestAddUoW() + { + _options.Object.UseUoW(); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + Assert.ThrowsException(() + => serviceProvider.GetRequiredService() + ); + } + + [TestMethod] + public void TestAddUoWAndUseSqlLite() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + Assert.IsNotNull(serviceProvider.GetRequiredService()); + } + + [TestMethod] + public void TestAddMultUoW() + { + _options.Object + .UseUoW(options => options.UseSqlite(_connection)) + .UseUoW(options => options.UseSqlite(_connection)); + + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestTransaction() + { + Mock uoW = new(); + Assert.IsTrue(new Transaction(uoW.Object).UnitOfWork!.Equals(uoW.Object)); + } + + [TestMethod] + public async Task TestSaveChangesAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + Mock customerDbContext = new(); + customerDbContext.Setup(dbContext => dbContext.SaveChangesAsync(default)).Verifiable(); + var uoW = new UnitOfWork(customerDbContext.Object, null); + await uoW.SaveChangesAsync(default); + customerDbContext.Verify(dbContext => dbContext.SaveChangesAsync(default), Times.Once); + } + + [TestMethod] + public async Task TestUseTranscationAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = serviceProvider.GetRequiredService(); + + var transaction = uoW.Transaction; + Users user = new Users() + { + Name = Guid.NewGuid().ToString() + }; + dbContext.Add(user); + await uoW.SaveChangesAsync(); + await uoW.RollbackAsync(); + + Assert.IsTrue(dbContext.User.ToList().Count() == 0); + } + + [TestMethod] + public async Task TestNotUseTranscationAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = new UnitOfWork(dbContext, null); + + Users user = new Users() + { + Name = Guid.NewGuid().ToString() + }; + dbContext.Add(user); + await uoW.SaveChangesAsync(); + await Assert.ThrowsExceptionAsync(async () => await uoW.RollbackAsync()); + } + + [TestMethod] + public async Task TestNotTransactionCommitAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = new UnitOfWork(dbContext, null); + await Assert.ThrowsExceptionAsync(async () => await uoW.CommitAsync()); + } + + [TestMethod] + public async Task TestCommitAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = new UnitOfWork(dbContext, null); + var user = new Users() + { + Name = "Tom" + }; + var transcation = uoW.Transaction; + dbContext.User.Add(user); + await uoW.SaveChangesAsync(); + await uoW.CommitAsync(); + + Assert.IsTrue(dbContext.User.ToList().Count == 1); + } + + [TestMethod] + public async Task TestOpenRollbackAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = serviceProvider.GetRequiredService(); + var user = new Users(); + var transcation = uoW.Transaction; + dbContext.User.Add(user); + await uoW.CommitAsync(); + + Assert.IsTrue(!await dbContext.User.AnyAsync()); + } + + [TestMethod] + public async Task TestAddLoggerAndOpenRollbackAsync() + { + _options.Object.Services.AddLogging(); + _options.Object.UseUoW(options => options.UseSqlite(_connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + var uoW = serviceProvider.GetRequiredService(); + var user = new Users(); + var transcation = uoW.Transaction; + dbContext.User.Add(user); + await uoW.CommitAsync(); + + Assert.IsTrue(!await dbContext.User.AnyAsync()); + } +} diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs new file mode 100644 index 000000000..489619ad4 --- /dev/null +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs @@ -0,0 +1,11 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Metadata.Builders; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System; +global using System.Threading.Tasks; diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests.csproj b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests.csproj new file mode 100644 index 000000000..6d24942c2 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests.csproj @@ -0,0 +1,14 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Students.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Students.cs new file mode 100644 index 000000000..6294dc285 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/Students.cs @@ -0,0 +1,28 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests; + +public class Students : AggregateRoot +{ + public Students() + { + RegisterTime = DateTime.UtcNow; + } + + public string SerialNumber { get; set; } = default!; + + public string Name { get; set; } + + public int Age { get; set; } + + public DateTime RegisterTime { get; private set; } + + /// + /// Test the case of the joint primary key error, no business value + /// + /// + public override IEnumerable<(string Name, object Value)> GetKeys() + => new List<(string Name, object Value)>() + { + ("SerialNumber", SerialNumber), + ("","") + }; +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs new file mode 100644 index 000000000..760e6cc39 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests/_Imports.cs @@ -0,0 +1 @@ +global using Masa.BuildingBlocks.Ddd.Domain.Entities; diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs new file mode 100644 index 000000000..ee9177990 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Courses.cs @@ -0,0 +1,19 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests; + +public class Courses : AggregateRoot +{ + public Courses() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; init; } + + public string Name { get; set; } + + public override IEnumerable<(string Name, object Value)> GetKeys() + => new List<(string Name, object Value)>() + { + ("Names",Name)//Demonstrate that a non-existent key is used as a joint primary key + }; +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj new file mode 100644 index 000000000..6d24942c2 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests.csproj @@ -0,0 +1,14 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs new file mode 100644 index 000000000..760e6cc39 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFindTests/_Imports.cs @@ -0,0 +1 @@ +global using Masa.BuildingBlocks.Ddd.Domain.Entities; diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs new file mode 100644 index 000000000..4e81d4234 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Entities/User.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.Entities; + +public class User : AggregateRoot +{ + public string Name { get; set; } + + public bool Gender { get; set; } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.csproj b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.csproj new file mode 100644 index 000000000..3025feb10 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.csproj @@ -0,0 +1,14 @@ +๏ปฟ + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs new file mode 100644 index 000000000..33f743fc3 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/Repositories/IUserRepository.cs @@ -0,0 +1,8 @@ +using Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.Entities; + +namespace Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.Repositories; + +public interface IUserRepository : IRepository +{ + +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs new file mode 100644 index 000000000..c1bd9c39c --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using Masa.BuildingBlocks.Ddd.Domain.Entities; +global using Masa.BuildingBlocks.Ddd.Domain.Repositories; diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Hobbies.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Hobbies.cs new file mode 100644 index 000000000..2f11d8202 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Hobbies.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests; + +public class Hobbies : AggregateRoot +{ + public string Name { get; private set; } + + private Hobbies() + { + Id = Guid.NewGuid(); + } + + public Hobbies(string name) : this() + { + Name = name; + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests.csproj b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests.csproj new file mode 100644 index 000000000..6d24942c2 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests.csproj @@ -0,0 +1,14 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + + diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/_Imports.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/_Imports.cs new file mode 100644 index 000000000..760e6cc39 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests/_Imports.cs @@ -0,0 +1 @@ +global using Masa.BuildingBlocks.Ddd.Domain.Entities; diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/BaseRepositoryTest.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/BaseRepositoryTest.cs new file mode 100644 index 000000000..d0e8648d2 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/BaseRepositoryTest.cs @@ -0,0 +1,78 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests; + +[TestClass] +public class BaseRepositoryTest : TestBase +{ + private IServiceCollection _services = default!; + private Assembly[] _assemblies; + private Mock _uoW; + private Mock _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + _assemblies = new Assembly[1] + { + typeof(BaseRepositoryTest).Assembly + }; + _uoW = new(); + _dispatcherOptions = new(); + _dispatcherOptions.Setup(options => options.Services).Returns(() => _services); + } + + [TestMethod] + public void TestNullServices() + { + Assert.ThrowsException(() => + { + _dispatcherOptions.Setup(options => options.Services).Returns(() => null!); + var options = _dispatcherOptions.Object.UseRepository(); + }); + } + + [TestMethod] + public void TestUseCustomRepositoryAndNotImplementation() + { + Mock uoW = new(); + _services.AddScoped(serviceProvider => uoW.Object); + + Assert.ThrowsException(() + => _dispatcherOptions.Object.UseRepository(typeof(TestBase).Assembly, typeof(IUserRepository).Assembly) + ); + } + + [TestMethod] + public void TestNullUnitOfWork() + { + var ex = Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(_assemblies); + }); + Assert.IsTrue(ex.Message == "Please add UoW first."); + } + + [TestMethod] + public void TestNullAssembly() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + + Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(null!); + }); + } + + [TestMethod] + public void TestAddMultRepository() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies).UseRepository(); + + var serviceProvider = _services.BuildServiceProvider(); + var repository = serviceProvider.GetServices>(); + Assert.IsTrue(repository.Count() == 1); + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Address.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Address.cs new file mode 100644 index 000000000..9670c9ad1 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Address.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Domain.Entities; + +public class Address : ValueObject +{ + public string Street { get; set; } + + public string City { get; set; } + + public string State { get; set; } + + public string Country { get; set; } + + public string ZipCode { get; set; } + + protected override IEnumerable GetEqualityValues() + { + yield return Street; + yield return City; + yield return State; + yield return Country; + yield return ZipCode; + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs new file mode 100644 index 000000000..f1d87fa69 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/OrderItem.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Domain.Entities; + +public class OrderItem : Entity +{ + public Guid OrderId { get; set; } + + public int ProductId { get; set; } + + public string ProductName { get; set; } + + public decimal UnitPrice { get; set; } + + public int Units { get; set; } + + public string PictureUrl { get; set; } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs new file mode 100644 index 000000000..5296bdb6d --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Entities/Orders.cs @@ -0,0 +1,40 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Domain.Entities; + +public class Orders : AuditAggregateRoot +{ + public int OrderNumber { get; set; } + + public DateTime OrderDate { get; private set; } + + public string OrderStatus { get; private set; } + + public string Description { get; set; } = default!; + + public List OrderItems { get; set; } + + public Orders() + { + this.OrderDate = DateTime.UtcNow; + this.OrderItems = new(); + this.OrderStatus = "Submitted"; + } + + public Orders(int id) : this() + { + base.Id = id; + } + + /// + /// Joint primary key, when this method does not exist, the primary key is Id + /// + /// + public override IEnumerable<(string Name, object Value)> GetKeys() + { + return new List<(string Name, object value)> + { + ("Id", Id), + ("OrderNumber", OrderNumber) + }; + } +} + diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs new file mode 100644 index 000000000..4d51ece3d --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Domain/Repositories/IOrderRepository.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Domain.Repositories; + +public interface IOrderRepository : IRepository +{ + Task AddAsync(Orders order); +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs new file mode 100644 index 000000000..492a6fb38 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/CustomDbContext.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Infrastructure; + +public class CustomDbContext : DbContext +{ + public DbSet Orders { get; set; } + + public DbSet Students { get; set; } + + public DbSet Courses { get; set; } + + public DbSet Hobbies { get; set; } + + public CustomDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + entityTypeBuilder => + { + entityTypeBuilder.HasKey("SerialNumber"); + }); + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs new file mode 100644 index 000000000..7deeaf231 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Infrastructure/Repositories/OrderRepository.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Infrastructure.Repositories; + +public class OrderRepository : Repository, IOrderRepository +{ + public OrderRepository(CustomDbContext context, IUnitOfWork unitOfWork) : base(context, unitOfWork) + { + } + + public async Task AddAsync(Orders order) + { + try + { + var transaction = base.Transaction; + await base.AddAsync(order, default); + await base.SaveChangesAsync(); + await base.CommitAsync(); + } + catch (Exception) + { + await base.RollbackAsync(); + } + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Tests.csproj b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Tests.csproj new file mode 100644 index 000000000..36dd5d050 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/Masa.Contrib.Ddd.Domain.Repository.EF.Tests.csproj @@ -0,0 +1,31 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs new file mode 100644 index 000000000..78c11e061 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs @@ -0,0 +1,273 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests; + +[TestClass] +public class RepositoryTest : TestBase +{ + private IServiceCollection _services = default!; + private Assembly[] _assemblies; + private Mock _uoW; + private Mock _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + _assemblies = new Assembly[1] + { + typeof(BaseRepositoryTest).Assembly + }; + _uoW = new(); + _uoW.Setup(uoW => uoW.UseTransaction).Returns(true); + _dispatcherOptions = new(); + _dispatcherOptions.Setup(options => options.Services).Returns(() => _services); + + } + + [TestMethod] + public async Task TestAsync() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + dbContext.SaveChanges(); + }); + _uoW.Setup(u => u.CommitAsync(default)).Verifiable(); + var orders = new List() + { + new Orders(1) + { + OrderNumber = 9999999, + Description = "Apple", + }, + new Orders(2) + { + OrderNumber = 9999999, + Description = "Apple2", + } + }; + + var repository = serviceProvider.GetRequiredService>(); + await repository.AddRangeAsync(orders); + await repository.UnitOfWork.SaveChangesAsync(); + + var orderList = await repository.GetListAsync(order => order.OrderNumber == 9999999, default); + Assert.IsNotNull(orderList); + Assert.IsTrue(orderList.Count() == 2); + + Assert.IsTrue((await repository.GetListAsync(order => order.Description == "Apple", default)).Count() == 1); + Assert.IsTrue(await repository.GetCountAsync(order => order.Description == "Apple", default) == 1); + + var huaweiOrder = await repository.FindAsync(order => order.Description == "Apple2"); + huaweiOrder!.Description = "HuaWei"; + huaweiOrder.OrderNumber = 9999998; + await repository.UnitOfWork.SaveChangesAsync(default); + + Assert.IsTrue((await repository.GetListAsync(order => order.Description == "Apple", default)).Count() == 1); + Assert.IsTrue(await repository.GetCountAsync(order => order.Description == "HuaWei", default) == 1); + + await repository.AddAsync(new Orders(3) + { + OrderNumber = 9999997, + Description = "Google" + }); + await repository.AddAsync(new Orders(4) + { + OrderNumber = 9999996, + Description = "Microsoft" + }); + + await repository.RemoveAsync(order => order.Description == "Apple", default); + await repository.UnitOfWork.SaveChangesAsync(default); + + var list = await repository.GetPaginatedListAsync(0, 10, null, default); + + Assert.IsTrue(list.Count == 3); + Assert.IsTrue(list[0].Description == "HuaWei"); + Assert.IsTrue(list[1].Description == "Google"); + Assert.IsTrue(list[2].Description == "Microsoft"); + + list = await repository.GetPaginatedListAsync(1, 10, null, default); + Assert.IsTrue(list.Count == 2); + Assert.IsTrue(list[0].Description == "Google"); + Assert.IsTrue(list[1].Description == "Microsoft"); + + list = await repository.GetPaginatedListAsync(order => order.Description != "Google", 0, 10, null, default); + Assert.IsTrue(list.Count == 2); + Assert.IsTrue(list[0].Description == "HuaWei"); + + var count = await repository.GetCountAsync(default); + Assert.IsTrue(count == 3); + + var huaWei = await repository.FindAsync(huaweiOrder.Id, huaweiOrder.OrderNumber); + await repository.RemoveAsync(huaWei!, default); + + await repository.UnitOfWork.SaveChangesAsync(default); + Assert.IsTrue(await repository.GetCountAsync(default) == 2); + + var remainingOrders = await repository.GetListAsync(default); + await repository.RemoveRangeAsync(remainingOrders); + await repository.UnitOfWork.SaveChangesAsync(default); + + Assert.IsTrue(await repository.GetCountAsync(default) == 0); + } + + [TestMethod] + public async Task TestTranscationFailedAsync() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + dbContext.Database.BeginTransaction(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + dbContext.SaveChanges(); + }); + _uoW.Setup(u => u.CommitAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.Commit(); + }); + _uoW.Setup(u => u.RollbackAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.RollbackAsync(); + }); + var repository = serviceProvider.GetRequiredService(); + + var order = new Orders() + { + OrderNumber = 1, + }; + await repository.AddAsync(order); + Assert.IsTrue(await repository.GetCountAsync(default) == 0); + } + + [TestMethod] + public async Task TestTranscationSucceededAsync() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + dbContext.SaveChanges(); + }); + _uoW.Setup(u => u.CommitAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.Commit(); + }); + _uoW.Setup(u => u.RollbackAsync(default)).Callback(() => + { + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.CurrentTransaction!.RollbackAsync(); + }); + var repository = serviceProvider.GetRequiredService(); + + var order = new Orders(1) + { + OrderNumber = 1, + Description = "Apple" + }; + await repository.AddAsync(order); + Assert.IsTrue(await repository.GetCountAsync(default) == 1); + } + + [TestMethod] + public async Task TestUpdateAsync() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); + _dispatcherOptions.Object.UseRepository(_assemblies); + + var serviceProvider = _services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); + + _uoW.Setup(u => u.SaveChangesAsync(default)).Callback(() => + { + dbContext.SaveChanges(); + }); + var repository = serviceProvider.GetRequiredService(); + + var order = new Orders(1) + { + OrderNumber = 1, + Description = "Apple" + }; + await repository.AddAsync(order, default); + await repository.UnitOfWork.SaveChangesAsync(default); + dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; + + order = await repository.FindAsync(order => order.Description == "Apple"); + order!.Description = "Apple Company"; + await repository.UnitOfWork.SaveChangesAsync(); + + order = await repository.FindAsync(order => order.Description == "Apple"); + Assert.IsNotNull(order); + + await repository.UpdateAsync(order, default); + await repository.UnitOfWork.SaveChangesAsync(); + dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; + Assert.IsTrue(await repository.GetCountAsync(default) == 1); + + order = await repository.FindAsync(order => order.Description == "Apple"); + Assert.IsNotNull(order); + + order.Description = "Apple Company"; + await repository.UpdateRangeAsync(new List() { order }, default); + await repository.UnitOfWork.SaveChangesAsync(); + + dbContext.Entry(order).State = Microsoft.EntityFrameworkCore.EntityState.Detached; + + order = await repository.FindAsync(order => order.Description == "Apple"); + Assert.IsNull(order); + } + + [TestMethod] + public void TestCompositeKeys() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Students).Assembly); + }); + } + + [TestMethod] + public void TestErrorCompositeKeys() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + Assert.ThrowsException(() => + { + _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Courses).Assembly); + }); + } + + [TestMethod] + public void TestPrivateEntity() + { + _services.AddScoped(typeof(IUnitOfWork), serviceProvider => _uoW.Object); + _services.AddDbContext(options => options.UseSqlite(_connection)); + _dispatcherOptions.Object.UseRepository(typeof(BaseRepositoryTest).Assembly, typeof(Hobbies).Assembly); + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/TestBase.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/TestBase.cs new file mode 100644 index 000000000..c73370a09 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/TestBase.cs @@ -0,0 +1,17 @@ +namespace Masa.Contrib.Ddd.Domain.Repository.EF.Tests; + +public class TestBase : IDisposable +{ + protected readonly SqliteConnection _connection; + + protected TestBase() + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + } + + public void Dispose() + { + _connection.Close(); + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/_Imports.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/_Imports.cs new file mode 100644 index 000000000..d2d9f49e1 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/_Imports.cs @@ -0,0 +1,23 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Ddd.Domain.Entities; +global using Masa.BuildingBlocks.Ddd.Domain.Entities.Auditing; +global using Masa.BuildingBlocks.Ddd.Domain.Repositories; +global using Masa.BuildingBlocks.Ddd.Domain.Values; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeys.Tests; +global using Masa.Contrib.Ddd.Domain.Repository.EF.CombinedKeysNoFind.Tests; +global using Masa.Contrib.Ddd.Domain.Repository.EF.CustomRepository.Tests.Repositories; +global using Masa.Contrib.Ddd.Domain.Repository.EF.Entity.Tests; +global using Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Domain.Entities; +global using Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Domain.Repositories; +global using Masa.Contrib.Ddd.Domain.Repository.EF.Tests.Infrastructure; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Reflection; + diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/DomainEventBusTest.cs b/test/Masa.Contrib.Ddd.Domain.Tests/DomainEventBusTest.cs new file mode 100644 index 000000000..49fe12b3b --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/DomainEventBusTest.cs @@ -0,0 +1,349 @@ +namespace Masa.Contrib.Ddd.Domain.Tests; + +[TestClass] +public class DomainEventBusTest +{ + private Assembly[] _defaultAssemblies = default!; + private IServiceCollection _services = default!; + private Mock _eventBus = default!; + private Mock _integrationEventBus = default!; + private Mock _uoW = default!; + private IOptions _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _defaultAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + _services = new ServiceCollection(); + _eventBus = new(); + _integrationEventBus = new(); + _uoW = new(); + _dispatcherOptions = Options.Create(new DispatcherOptions(new ServiceCollection())); + } + + [TestMethod] + public void TestGetAllEventTypes() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var eventTypes = assemblies.SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)); + _eventBus.Setup(eventBus => eventBus.GetAllEventTypes()).Returns(() => eventTypes); + _dispatcherOptions.Value.Assemblies = _defaultAssemblies; + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + + Assert.IsTrue(domainEventBus.GetAllEventTypes().Count() == eventTypes.Count(), ""); + } + + [TestMethod] + public async Task TestPublishDomainEventAsync() + { + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + + var domainEvent = new PaymentSucceededDomainEvent(new Random().Next(10000, 1000000).ToString()); + await domainEventBus.PublishAsync(domainEvent); + + _eventBus.Verify(eventBus => eventBus.PublishAsync(domainEvent), Times.Once, "PublishAsync is executed multiple times"); + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(domainEvent), Times.Never, "integrationEventBus should not be executed"); + Assert.IsTrue(domainEvent.UnitOfWork!.Equals(_uoW.Object)); + } + + [TestMethod] + public async Task TestPublishIntegrationDomainEventAsync() + { + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _integrationEventBus.Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())).Verifiable(); + var integrationDomainEvent = new PaymentFailedIntegrationDomainEvent() + { + OrderId = new Random().Next(10000, 1000000).ToString() + }; + await domainEventBus.PublishAsync(integrationDomainEvent); + + _eventBus.Verify(eventBus => eventBus.PublishAsync(integrationDomainEvent), Times.Never, "eventBus should not be executed"); + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Once, " PublishAsync is executed multiple times"); + } + + [TestMethod] + public async Task TestPublishDomainCommandAsync() + { + _uoW.Setup(u => u.CommitAsync(default)).Verifiable(); + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())) + .Callback((domainEvent) => + { + Mock> userRepository = new(); + var user = new Users() + { + Name = "Jim" + }; + userRepository.Setup(repository => repository.AddAsync(It.IsAny(), CancellationToken.None)).Verifiable(); + domainEvent.UnitOfWork!.CommitAsync(); + }); + + var @command = new CreateProductDomainCommand() + { + Name = "Phone" + }; + await domainEventBus.PublishAsync(@command); + + _eventBus.Verify(eventBus => eventBus.PublishAsync(@command), Times.Once, "PublishAsync is executed multiple times"); + _uoW.Verify(u => u.CommitAsync(default), Times.Once); + } + + [TestMethod] + public void TestAddMultDomainEventBusAsync() + { + _services.AddScoped(serviceProvider => _eventBus.Object); + _services.AddScoped(serviceProvider => _integrationEventBus.Object); + _services.AddScoped(serviceProvider => _uoW.Object); + + _services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }).AddDomainEventBus(); + var serviceProvider = _services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetServices>().Count() == 1); + } + + [TestMethod] + public void TestNotUseEventBus() + { + var ex = Assert.ThrowsException(() + => _services.AddDomainEventBus() + ); + Assert.IsTrue(ex.Message == "Please add EventBus first."); + } + + [TestMethod] + public void TestNotUseUnitOfWork() + { + var eventBus = new Mock(); + _services.AddScoped(serviceProvider => eventBus.Object); + + var ex = Assert.ThrowsException(() + => _services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }) + ); + Assert.IsTrue(ex.Message == "Please add UoW first."); + } + + [TestMethod] + public void TestNotUseIntegrationEventBus() + { + var services = new ServiceCollection(); + + var eventBus = new Mock(); + services.AddScoped(serviceProvider => eventBus.Object); + + var uoW = new Mock(); + services.AddScoped(serviceProvider => uoW.Object); + + var ex = Assert.ThrowsException(() + => services.AddDomainEventBus(options => options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }) + ); + Assert.IsTrue(ex.Message == "Please add IntegrationEventBus first."); + } + + [TestMethod] + public void TestNullAssembly() + { + Assert.ThrowsException(() => _dispatcherOptions.Value.Assemblies = null!); + } + + [TestMethod] + public void TestNotRepository() + { + var services = new ServiceCollection(); + + var eventBus = new Mock(); + services.AddScoped(serviceProvider => eventBus.Object); + + var uoW = new Mock(); + services.AddScoped(serviceProvider => uoW.Object); + + var integrationEventBus = new Mock(); + services.AddScoped(serviceProvider => integrationEventBus.Object); + + Assert.ThrowsException(() => + { + services.AddDomainEventBus(options => + { + options.Assemblies = new Assembly[1] { typeof(Users).Assembly }; + }); + }); + } + + [TestMethod] + public void TestUserRepository() + { + var services = new ServiceCollection(); + + var eventBus = new Mock(); + services.AddScoped(serviceProvider => eventBus.Object); + + var uoW = new Mock(); + services.AddScoped(serviceProvider => uoW.Object); + + var integrationEventBus = new Mock(); + services.AddScoped(serviceProvider => integrationEventBus.Object); + + Mock> repository = new(); + services.AddScoped(serviceProvider => repository.Object); + services.AddDomainEventBus(options => + { + options.Assemblies = new Assembly[2] { typeof(Users).Assembly, typeof(DomainEventBusTest).Assembly }; + }); + } + + [TestMethod] + public async Task TestPublishQueueAsync() + { + var domainEvent = new PaymentSucceededDomainEvent("ef5f84db-76e4-4c79-9815-99a1543b6589"); + var integrationDomainEvent = new PaymentFailedIntegrationDomainEvent { OrderId = "d65c1a0c-6e44-40ce-9737-738fa1dcdab4" }; + + _eventBus + .Setup(eventBus => eventBus.PublishAsync(It.IsAny())) + .Callback(() => + { + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Never, "Sent in the wrong order"); + }); + + _integrationEventBus + .Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())) + .Callback(() => + { + _eventBus.Verify(eventBus => eventBus.PublishAsync((IDomainEvent)domainEvent), Times.Once, "Sent in the wrong order"); + }); + + var uoW = new Mock(); + uoW.Setup(u => u.CommitAsync(default)).Verifiable(); + + var options = Options.Create(new DispatcherOptions(_services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); + + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, uoW.Object, options); + + await domainEventBus.Enqueue(domainEvent); + await domainEventBus.Enqueue(integrationDomainEvent); + + await domainEventBus.PublishQueueAsync(); + + _eventBus.Verify(eventBus => eventBus.PublishAsync((IDomainEvent)domainEvent), Times.Once, "Sent in the wrong order"); + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(integrationDomainEvent), Times.Never, "Sent in the wrong order"); + } + + [TestMethod] + public async Task TestPublishDomainQueryAsync() + { + var services = new ServiceCollection(); + + var eventBus = new Mock(); + eventBus.Setup(e => e.PublishAsync(It.IsAny())) + .Callback(query => + { + if (query.ProductId == "2f8d4c3c-1736-4e56-a188-f865da6a63d1") + query.Result = "apple"; + }); + var integrationEventBus = new Mock(); + var uoW = new Mock(); + uoW.Setup(u => u.CommitAsync(default)).Verifiable(); + + var options = Options.Create(new DispatcherOptions(services) { Assemblies = AppDomain.CurrentDomain.GetAssemblies() }); + + var domainEventBus = new DomainEventBus(eventBus.Object, integrationEventBus.Object, uoW.Object, options); + + var query = new ProductItemDomainQuery() { ProductId = "2f8d4c3c-1736-4e56-a188-f865da6a63d1" }; + + await domainEventBus.PublishAsync(query); + Assert.IsTrue(query.Result == "apple"); + } + + [TestMethod] + public async Task TestCommitAsync() + { + var services = new ServiceCollection(); + + _uoW.Setup(uow => uow.CommitAsync(CancellationToken.None)).Verifiable(); + Mock> options = new(); + + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, options.Object); + await domainEventBus.CommitAsync(CancellationToken.None); + + _uoW.Verify(u => u.CommitAsync(default), Times.Once, "CommitAsync must be called only once"); + } + + [TestMethod] + public void TestParameterInitialization() + { + var id = Guid.NewGuid(); + var createTime = DateTime.UtcNow; + + var domainCommand = new DomainCommand(); + Assert.IsTrue(domainCommand.Id != default); + Assert.IsTrue(domainCommand.CreationTime != default && domainCommand.CreationTime >= createTime); + + domainCommand = new DomainCommand(id, createTime); + Assert.IsTrue(domainCommand.Id == id); + Assert.IsTrue(domainCommand.CreationTime == createTime); + + var domainEvent = new DomainEvent(); + Assert.IsTrue(domainEvent.Id != default); + Assert.IsTrue(domainEvent.CreationTime != default && domainEvent.CreationTime >= createTime); + + domainEvent = new DomainEvent(id, createTime); + Assert.IsTrue(domainEvent.Id == id); + Assert.IsTrue(domainEvent.CreationTime == createTime); + + var domainQuery = new ProductItemDomainQuery() + { + ProductId = Guid.NewGuid().ToString() + }; + Assert.IsTrue(domainQuery.Id != default); + Assert.IsTrue(domainQuery.CreationTime != default && domainQuery.CreationTime >= createTime); + } + + [TestMethod] + public void TestDomainQueryUnitOfWork() + { + var domainQuery = new ProductItemDomainQuery() + { + ProductId = Guid.NewGuid().ToString() + }; + Assert.ThrowsException(() => + { + domainQuery.UnitOfWork = _uoW.Object; + }); + Assert.IsNull(domainQuery.UnitOfWork); + } + + [TestMethod] + public async Task TestDomainServiceAsync() + { + _integrationEventBus.Setup(integrationEventBus => integrationEventBus.PublishAsync(It.IsAny())).Verifiable(); + + _services.AddDomainEventBus(options => + { + options.Assemblies = new Assembly[1] { typeof(DomainEventBusTest).Assembly }; + options.Services.AddScoped(serviceProvider => _eventBus.Object); + options.Services.AddScoped(serviceProvider => _integrationEventBus.Object); + options.Services.AddScoped(serviceProvider => _uoW.Object); + }); + var serviceProvider = _services.BuildServiceProvider(); + + var userDomainService = serviceProvider.GetRequiredService(); + var domainIntegrationEvent = new RegisterUserSucceededDomainIntegrationEvent() { Account = "Tom" }; + await userDomainService.RegisterUserSucceededAsync(domainIntegrationEvent); + + _integrationEventBus.Verify(integrationEventBus => integrationEventBus.PublishAsync(domainIntegrationEvent), Times.Once); + } + + [TestMethod] + public async Task TestPublishEvent() + { + var domainEventBus = new DomainEventBus(_eventBus.Object, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + + var @event = new ForgetPasswordEvent() + { + Account = "Tom" + }; + await domainEventBus.PublishAsync(@event); + _eventBus.Verify(eventBus => eventBus.PublishAsync(@event), Times.Once); + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/DomainIntegrationEventBusTest.cs b/test/Masa.Contrib.Ddd.Domain.Tests/DomainIntegrationEventBusTest.cs new file mode 100644 index 000000000..73a4c37b2 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/DomainIntegrationEventBusTest.cs @@ -0,0 +1,49 @@ +๏ปฟnamespace Masa.Contrib.Ddd.Domain.Tests; + +[TestClass] +public class DomainIntegrationEventBus +{ + private Assembly[] _defaultAssemblies = default!; + private IServiceCollection _services = default!; + private Mock _integrationEventBus = default!; + private Mock _uoW = default!; + private IOptions _dispatcherOptions = default!; + + [TestInitialize] + public void Initialize() + { + _defaultAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + _services = new ServiceCollection(); + _integrationEventBus = new(); + _integrationEventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + _uoW = new(); + _dispatcherOptions = Options.Create(new DispatcherOptions(new ServiceCollection())); + } + + [TestMethod] + public async Task PublishQueueAsync() + { + _services.AddEventBus(opt => + { + opt.Assemblies = _defaultAssemblies; + }); + var serviceProvider = _services.BuildServiceProvider(); + var eventBus = serviceProvider.GetRequiredService(); + var payment = new + { + orderId = Guid.NewGuid(), + money = 100, + payTime = DateTime.UtcNow + }; + var domainEventBus = new DomainEventBus(eventBus, _integrationEventBus.Object, _uoW.Object, _dispatcherOptions); + + var domainEvent = new PaymentSucceededDomainEvent(payment.orderId.ToString()); + await domainEventBus.Enqueue(domainEvent); + + var integraionDomainEvent = new PaymentSucceededIntegraionDomainEvent(payment.orderId.ToString(), payment.money, payment.payTime); + await domainEventBus.Enqueue(integraionDomainEvent); + await domainEventBus.PublishQueueAsync(); + Assert.IsTrue(domainEvent.Result); + _integrationEventBus.Verify(eventBus => eventBus.PublishAsync(It.IsAny()), Times.Once); + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/CreateProductDomainCommand.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/CreateProductDomainCommand.cs new file mode 100644 index 000000000..375e79629 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/CreateProductDomainCommand.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public record CreateProductDomainCommand : DomainCommand +{ + public string Name { get; set; } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/ForgetPasswordEvent.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/ForgetPasswordEvent.cs new file mode 100644 index 000000000..4eff2a5e5 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/ForgetPasswordEvent.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public class ForgetPasswordEvent : IEvent +{ + public Guid Id { get; init; } = Guid.NewGuid(); + + public DateTime CreationTime { get; init; } = DateTime.UtcNow; + + public string Account { get; set; } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs new file mode 100644 index 000000000..f362c3ad0 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentFailedIntegrationDomainEvent.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public record PaymentFailedIntegrationDomainEvent : IntegrationDomainEvent +{ + public string OrderId { get; set; } + + public override string Topic { get; set; } = nameof(PaymentFailedIntegrationDomainEvent); +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededDomainEvent.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededDomainEvent.cs new file mode 100644 index 000000000..556cacee9 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededDomainEvent.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public record PaymentSucceededDomainEvent(string OrderId) : DomainEvent +{ + public bool Result { get; set; } = false; +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs new file mode 100644 index 000000000..9b99a6463 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/PaymentSucceededIntegraionDomainEvent.cs @@ -0,0 +1,6 @@ +๏ปฟnamespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public record PaymentSucceededIntegraionDomainEvent(string OrderId, decimal Money, DateTime PayTime) : IntegrationDomainEvent +{ + public override string Topic { get; set; } = nameof(PaymentSucceededIntegraionDomainEvent); +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/ProductItemDomainQuery.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/ProductItemDomainQuery.cs new file mode 100644 index 000000000..19f10ab93 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/ProductItemDomainQuery.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public record ProductItemDomainQuery : DomainQuery +{ + public string ProductId { get; set; } + + public override string Result { get; set; } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs new file mode 100644 index 000000000..2824d60cf --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Events/RegisterUserSucceededDomainIntegrationEvent.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Events; + +public record RegisterUserSucceededDomainIntegrationEvent : IntegrationDomainEvent +{ + public override string Topic { get; set; } = nameof(RegisterUserSucceededDomainIntegrationEvent); + + public string Account { get; init; } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs new file mode 100644 index 000000000..1cd12d119 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Handlers/PaymentSucceededDomainEventHandller.cs @@ -0,0 +1,19 @@ +๏ปฟnamespace Masa.Contrib.Ddd.Domain.Tests.Handlers; + +public class PaymentSucceededDomainEventHandller +{ + private readonly ILogger? _logger; + + public PaymentSucceededDomainEventHandller(ILogger? logger = null) + { + _logger = logger; + } + + [EventHandler] + public Task PaymentSucceeded(PaymentSucceededDomainEvent domainEvent) + { + _logger?.LogInformation("PaymentSucceeded: OrderId: {OrderId}", domainEvent.OrderId); + domainEvent.Result = true; + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Masa.Contrib.Ddd.Domain.Tests.csproj b/test/Masa.Contrib.Ddd.Domain.Tests/Masa.Contrib.Ddd.Domain.Tests.csproj new file mode 100644 index 000000000..0111e4b29 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Masa.Contrib.Ddd.Domain.Tests.csproj @@ -0,0 +1,29 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/Services/UserDomainService.cs b/test/Masa.Contrib.Ddd.Domain.Tests/Services/UserDomainService.cs new file mode 100644 index 000000000..af894f0a6 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/Services/UserDomainService.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Ddd.Domain.Tests.Services; + +public class UserDomainService : DomainService +{ + public UserDomainService(IDomainEventBus eventBus) : base(eventBus) + { + } + + public async Task RegisterUserSucceededAsync(RegisterUserSucceededDomainIntegrationEvent domainIntegrationEvent) + { + // TODO Simulate a successful message for registered users + + await EventBus.PublishAsync(domainIntegrationEvent); + return "succeed"; + } +} diff --git a/test/Masa.Contrib.Ddd.Domain.Tests/_Imports.cs b/test/Masa.Contrib.Ddd.Domain.Tests/_Imports.cs new file mode 100644 index 000000000..5c4ca40e5 --- /dev/null +++ b/test/Masa.Contrib.Ddd.Domain.Tests/_Imports.cs @@ -0,0 +1,16 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Ddd.Domain.Events; +global using Masa.BuildingBlocks.Ddd.Domain.Repositories; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.Contrib.Ddd.Domain.Events; +global using Masa.Contrib.Ddd.Domain.Tests.Events; +global using Masa.Contrib.Ddd.Domain.Tests.Services; +global using Masa.Contribs.Ddd.Domain.Entities.Tests; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System.Reflection; +global using Masa.Contrib.Dispatcher.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs new file mode 100644 index 000000000..c9cea26f1 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Benchmarks.cs @@ -0,0 +1,61 @@ +namespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests; + +[SimpleJob(RunStrategy.ColdStart, RuntimeMoniker.Net60, targetCount: 100)] +[MinColumn, MaxColumn, MeanColumn, MedianColumn] +public class Benchmarks +{ + private RegisterUserEvent _userEvent; + private ForgetPasswordEvent _forgetPasswordEvent; + private IServiceProvider _serviceProvider; + private IEventBus _eventBus; + + [GlobalSetup] + public void GlobalSetup() + { + IServiceCollection services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.ClearProviders()); + services.AddEventBus(); + _serviceProvider = services.BuildServiceProvider(); + _eventBus = _serviceProvider.GetRequiredService(); + _userEvent = new RegisterUserEvent() + { + Name = "tom", + PhoneNumber = "18888888888" + }; + _forgetPasswordEvent = new ForgetPasswordEvent() + { + Name = "lisa", + PhoneNumber = "19999999999" + }; + } + + [Benchmark] + public async Task Direct() + { + var _couponHandler = new CouponHandler(_serviceProvider); + await _couponHandler.SendCoupon(_userEvent); + } + + [Benchmark] + public async Task LambdaTree() + { + await _eventBus.PublishAsync(_userEvent); + } + + [Benchmark] + public async Task SendForgetPasseordByDirect() + { + var emailNoticeHandler = new NoticeEmailHandler(_serviceProvider); + var smsNoticeHandler = new NoticeSmsHandler(_serviceProvider); + var sendCouponHandler = new SendCouponHandler(_serviceProvider); + await emailNoticeHandler.HandleAsync(_forgetPasswordEvent); + await smsNoticeHandler.HandleAsync(_forgetPasswordEvent); + await sendCouponHandler.HandleAsync(_forgetPasswordEvent); + } + + [Benchmark] + public async Task SendForgetPasseordByInterfaces() + { + await _eventBus.PublishAsync(_forgetPasswordEvent); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs new file mode 100644 index 000000000..14d81fde1 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/CouponHandler.cs @@ -0,0 +1,50 @@ +namespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; + +public class CouponHandler +{ + private readonly ILogger? _logger; + + public CouponHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); + + [EventHandler(Order = 10, FailureLevels = FailureLevels.ThrowAndCancel)] + public Task SendCoupon(RegisterUserEvent @event) + { + _logger?.LogInformation("------Send Coupon------"); + + var num = new Random().Next(1, 3); + if (num % 2 == 0) + { + //throw new Exception("Failed to send coupons"); + } + + return Task.CompletedTask; + } + + [EventHandler(Order = 20, FailureLevels = FailureLevels.Ignore)] + public Task SendNotice(RegisterUserEvent @event) + { + _logger?.LogInformation("------Send Coupon------"); + + var num = new Random().Next(1, 3); + if (num % 2 == 0) + { + //throw new Exception("Failed to send coupons"); + } + + return Task.CompletedTask; + } + + [EventHandler(Order = 10, RetryTimes = 5, IsCancel = true)] + public Task CancelSendCoupon(RegisterUserEvent @event) + { + _logger?.LogInformation("------Cancel Send Coupon------"); + + var num = new Random().Next(1, 3); + if (num % 2 == 0) + { + //throw new Exception("Failed to cancel send coupons"); + } + + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs new file mode 100644 index 000000000..3dac7a914 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/EventHandlers/NoticeHandler.cs @@ -0,0 +1,63 @@ +๏ปฟusing Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; + +namespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; + +public class SendCouponHandler : ISagaEventHandler +{ + private readonly ILogger? _logger; + + public SendCouponHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); + + [EventHandler(Order = 10)] + public Task HandleAsync(ForgetPasswordEvent @event) + { + _logger?.LogInformation("------Send Coupon------"); + return Task.CompletedTask; + } + + public Task CancelAsync(ForgetPasswordEvent @event) + { + _logger?.LogInformation("------Cancel Coupon------"); + return Task.CompletedTask; + } +} + +public class NoticeSmsHandler : ISagaEventHandler +{ + private readonly ILogger? _logger; + + public NoticeSmsHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); + + [EventHandler(Order = 20)] + public Task HandleAsync(ForgetPasswordEvent @event) + { + _logger?.LogInformation("------Send Sms Notice------"); + return Task.CompletedTask; + } + + public Task CancelAsync(ForgetPasswordEvent @event) + { + _logger?.LogInformation("------Cancel Sms Notice------"); + return Task.CompletedTask; + } +} + +public class NoticeEmailHandler : ISagaEventHandler +{ + private readonly ILogger? _logger; + + public NoticeEmailHandler(IServiceProvider serviceProvider) => _logger = serviceProvider.GetService>(); + + [EventHandler(Order = 30)] + public Task HandleAsync(ForgetPasswordEvent @event) + { + _logger?.LogInformation("------Send Email Notice------"); + return Task.CompletedTask; + } + + public Task CancelAsync(ForgetPasswordEvent @event) + { + _logger?.LogInformation("------Cancel Email Notice------"); + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs new file mode 100644 index 000000000..bf7c336ff --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/ForgetPasswordEvent.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; + +public record ForgetPasswordEvent : Event +{ + public string Name { get; set; } + + public string PhoneNumber { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs new file mode 100644 index 000000000..bc0d0a8dc --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Events/RegisterUserEvent.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; + +public record RegisterUserEvent : Event +{ + public string Name { get; set; } + + public string PhoneNumber { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs new file mode 100644 index 000000000..79f4456ef --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Extensions/Middleware/LoggingMiddleware.cs @@ -0,0 +1,15 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Middleware; + +public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent +{ + private readonly ILogger>? _logger; + public LoggingMiddleware(ILogger>? logger = null) => _logger = logger; + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + var eventType = @event.GetType(); + _logger?.LogInformation("----- Handling command {CommandName} ({@Command})", eventType.FullName, @event); + + await next(); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj new file mode 100644 index 000000000..d51a2338a --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.csproj @@ -0,0 +1,22 @@ + + + + Exe + net6.0 + AnyCPU + false + enable + false + enable + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs new file mode 100644 index 000000000..750508c10 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/Program.cs @@ -0,0 +1,14 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests; + +class Program +{ + static void Main(string[] args) + { + var config = DefaultConfig.Instance + .AddValidator(ExecutionValidator.FailOnError) + .WithOptions(ConfigOptions.DisableOptimizationsValidator); + BenchmarkRunner.Run(config); + + Console.ReadLine(); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs new file mode 100644 index 000000000..e5ce70b81 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests/_Imports.cs @@ -0,0 +1,15 @@ +global using BenchmarkDotNet.Attributes; +global using BenchmarkDotNet.Configs; +global using BenchmarkDotNet.Engines; +global using BenchmarkDotNet.Jobs; +global using BenchmarkDotNet.Running; +global using BenchmarkDotNet.Validators; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.EventHandlers; +global using Masa.Contrib.Dispatcher.Events.BenchmarkDotnet.Tests.Extensions.Events; +global using Masa.Contrib.Dispatcher.Events.Enums; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using System; +global using System.Threading.Tasks; + diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs new file mode 100644 index 000000000..a534bca40 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/EventHandlers/AddGoodsHandler.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.EventHandlers; + +public class AddGoodsHandler +{ + [EventHandler] + public void AddGoods(AddGoodsEvent @event, ILogger? logger) + { + logger?.LogInformation($"add goods log,GoodsId:{@event.GoodsId},GoodsName:{@event.GoodsName},CategoryId:{@event.CategoryId}"); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs new file mode 100644 index 000000000..983542861 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Events/AddGoodsEvent.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; + +public record AddGoodsEvent : Event +{ + public string GoodsId { get; set; } + + public string CategoryId { get; set; } + + public string GoodsName { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs new file mode 100644 index 000000000..d59f3421d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs new file mode 100644 index 000000000..985d1b6c1 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/EventHandlers/DeleteGoodsHandler.cs @@ -0,0 +1,10 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.EventHandlers; + +public class DeleteGoodsHandler +{ + [EventHandler] + public void DeleteGoods() + { + + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs new file mode 100644 index 000000000..012098162 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Events/DeleteGoodsEvent.cs @@ -0,0 +1,6 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.Events; + +public record DeleteGoodsEvent : Event +{ + public string GoodsId { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests/_Imports.cs new file mode 100644 index 000000000..e69de29bb diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs new file mode 100644 index 000000000..76b8231f8 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/EventHandlers/AddCatalogHandler.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.EventHandlers; + +public class AddCatalogHandler +{ + /// + /// The method name of this method can be named according to the actual business + /// but we recommend HandlerAsync or CancelAsync if the business is single + /// + /// + /// + [EventHandler] + public Task HandleAsync(AddCatalogEvent @event) + { + return Task.FromResult("success"); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs new file mode 100644 index 000000000..042bd7d99 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Events/AddCatalogEvent.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; + +public class AddCatalogEvent +{ + public string GoodsId { get; set; } + + public int Count { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs new file mode 100644 index 000000000..c580892d7 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests/_Imports.cs @@ -0,0 +1 @@ +global using Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs new file mode 100644 index 000000000..0a5fa30f7 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/EventHandlers/AddBasketHandler.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.EventHandlers; + +public class AddBasketHandler +{ + private readonly ILogger? _logger; + public AddBasketHandler(ILogger? logger) => _logger = logger; + + [EventHandler] + public Task AddLog(AddBasketEvent @event) + { + _logger?.LogInformation($"add basket log๏ผšGoogdsId๏ผš{@event.GoodsId}๏ผŒcount๏ผš{@event.Count}"); + return Task.FromResult("success"); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs new file mode 100644 index 000000000..b8f46cc88 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Events/AddBasketEvent.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.Events; + +public record AddBasketEvent : Event +{ + public string GoodsId { get; set; } + + public int Count { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs new file mode 100644 index 000000000..9edac0382 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs new file mode 100644 index 000000000..16504aeb4 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/EventHandlers/UserEventHandler.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.EventHandlers; + +public class UserEventHandler +{ + [EventHandler(IsCancel = true)] + public void BindPhoneNumber(BindPhoneNumberEvent @event) + { + + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs new file mode 100644 index 000000000..d1f07a766 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Events/BindPhoneNumberEvent.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; + +public record BindPhoneNumberEvent : Event +{ + public string AccountId { get; set; } + + public string PhoneNumber { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs new file mode 100644 index 000000000..053d66794 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests/_Imports.cs @@ -0,0 +1 @@ +global using Masa.Contrib.Dispatcher.Events.OnlyCancelHandler.Tests.Events; diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs new file mode 100644 index 000000000..11bc7f29a --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/EventHandlers/EditCategoryHandler.cs @@ -0,0 +1,21 @@ +namespace Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.EventHandlers; + +public class EditCategoryHandler : ISagaEventHandler +{ + private readonly ILogger? _logger; + public EditCategoryHandler(ILogger? logger = null) => _logger = logger; + + [EventHandler(10)] + public Task CancelAsync(EditCategoryEvent @event) + { + _logger?.LogInformation($"cancel edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); + return Task.CompletedTask; + } + + [EventHandler(20)] + public Task HandleAsync(EditCategoryEvent @event) + { + _logger?.LogInformation($"edit category log,CategoryId:{@event.CategoryId},Name:{@event.CategoryName}"); + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs new file mode 100644 index 000000000..d66e07978 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Events/EditCategoryEvent.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; + +public record EditCategoryEvent : Event +{ + public string CategoryId { get; set; } + + public string CategoryName { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs new file mode 100644 index 000000000..af4506190 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests/_Imports.cs @@ -0,0 +1,3 @@ +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs new file mode 100644 index 000000000..3872e5b0f --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/EventHandlers/OrderStockConfirmedHandler.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.EventHandlers; + +public class OrderStockConfirmedHandler +{ + private readonly ILogger? _logger; + + public OrderStockConfirmedHandler(ILogger? logger = null) => _logger = logger; + + [EventHandler(-10)] + public void AddLog(OrderStockConfirmedEvent @event) + { + _logger?.LogInformation($"add order stock confirmed log,orderId:{@event.OrderId}"); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs new file mode 100644 index 000000000..ceb2a222c --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Events/OrderStockConfirmedEvent.cs @@ -0,0 +1,6 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; + +public record OrderStockConfirmedEvent : Event +{ + public string OrderId { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs new file mode 100644 index 000000000..8162d5067 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests/_Imports.cs @@ -0,0 +1,2 @@ +global using Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs new file mode 100644 index 000000000..a0efe737e --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/EventHandlers/EditGoodsHandler.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.EventHandlers; + +public class EditGoodsHandler : IEventHandler +{ + private readonly ILogger? _logger; + public EditGoodsHandler(ILogger? logger) => _logger = logger; + + [EventHandler(-10)] + public Task HandleAsync(EditGoodsEvent @event) + { + _logger?.LogInformation($"edit goods log๏ผŒGoodsId:{@event.GoodsId},Name:{@event.GoodsName},CategoryId:{@event.CategoryId}"); + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs new file mode 100644 index 000000000..4887ebc94 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Events/EditGoodsEvent.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; + +public record EditGoodsEvent : Event +{ + public string GoodsId { get; set; } + + public string CategoryId { get; set; } + + public string GoodsName { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj new file mode 100644 index 000000000..b6c588b8d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs new file mode 100644 index 000000000..cd6f37d5b --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests/_Imports.cs @@ -0,0 +1,3 @@ +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; +global using Microsoft.Extensions.Logging; diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs new file mode 100644 index 000000000..abe9b8129 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs @@ -0,0 +1,122 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests; + +[TestClass] +public class AssemblyResolutionTests +{ + [TestMethod] + public void TestResolveEventBus() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + services.AddEventBus(); + var serviceProvider = services.BuildServiceProvider(); + var eventBus = serviceProvider.GetService(); + Assert.IsNotNull(eventBus, "Event bus injection failed"); + Assert.IsNotNull(eventBus.GetAllEventTypes()); + } + + [TestMethod] + public void TestAddDefaultAssembly() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = AppDomain.CurrentDomain.GetAssemblies()); + } + + [TestMethod] + public void TestAddNullAssembly() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + Assert.ThrowsException(() => + { + services.AddEventBus(options => options.Assemblies = null!); + }); + } + + [TestMethod] + public void TestAddEmptyAssembly() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + Assert.ThrowsException(() => + { + services.AddEventBus(options => options.Assemblies = new Assembly[0]); + }); + } + + [TestMethod] + public void TestEventBusByAddNullAssembly() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + Assert.ThrowsException(() => + { + services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = null!); + }); + } + + [TestMethod] + public void TestEventBusByAddEmptyAssembly() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + Assert.ThrowsException(() => + { + services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = new Assembly[0]); + }); + } + + [TestMethod] + public void TestEventBus() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + services.AddTestEventBus(ServiceLifetime.Scoped); + } + + [TestMethod] + public void TestUseEventBus() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + var options = new DispatcherOptions(services); + options.UseEventBus(); + + var eventBus = services.BuildServiceProvider().GetService(); + Assert.IsNotNull(eventBus); + } + + [TestMethod] + public void TestAddMultEventBus() + { + var services = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + var options = new DispatcherOptions(services); + options.UseEventBus().UseEventBus(); + + Assert.IsTrue(services.BuildServiceProvider().GetServices().Count() == 1); + + var services2 = new ServiceCollection(); + services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + services2.AddTestEventBus(ServiceLifetime.Scoped) + .AddTestEventBus(ServiceLifetime.Scoped); + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseEventBusAndNullServices() + { + var options = new DispatcherOptions(null!); + Assert.ThrowsException(() => options.UseEventBus()); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/ChoreTest.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/ChoreTest.cs new file mode 100644 index 000000000..359b5e877 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/ChoreTest.cs @@ -0,0 +1,102 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests; + +[TestClass] +public class ChoreTest : TestBase +{ + private readonly IEventBus _eventBus; + public ChoreTest() + { + _eventBus = _serviceProvider.GetRequiredService(); + } + + [DataTestMethod] + [DataRow("jordan", "19999999999", 1, "A very ordinary boy who likes code")] + [DataRow("tom", "18888888888", 0, "A girl who likes to dance")] + public async Task TestNotHandler(string account, string phone, int gender, string abstracts) + { + AddUserEvent @event = new AddUserEvent() + { + Account = account, + Phone = phone, + Gender = gender == 1, + Abstract = abstracts + }; + await Assert.ThrowsExceptionAsync(async () => + { + await _eventBus.PublishAsync(@event); + }); + } + + [TestMethod] + public void TestDispatchHandlerConstructor() + { + var dispatchHandler = new EventHandlerAttribute(); + Assert.IsTrue(dispatchHandler.Order.Equals(int.MaxValue)); + + dispatchHandler = new EventHandlerAttribute(1); + Assert.IsTrue(dispatchHandler.Order.Equals(1)); + + dispatchHandler = new EventHandlerAttribute(1, true); + Assert.IsTrue(dispatchHandler.Order.Equals(1)); + Assert.IsTrue(dispatchHandler.EnableRetry.Equals(true)); + + dispatchHandler = new EventHandlerAttribute(2, FailureLevels.Ignore); + Assert.IsTrue(dispatchHandler.Order.Equals(2)); + Assert.IsTrue(dispatchHandler.EnableRetry.Equals(false)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.Ignore)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); + + dispatchHandler = new EventHandlerAttribute(10, true, false); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.EnableRetry.Equals(true)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(3)); + + dispatchHandler = new EventHandlerAttribute(10, true, false, 5); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.EnableRetry.Equals(true)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(5)); + + dispatchHandler = new EventHandlerAttribute(10, false, false, 5); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.EnableRetry.Equals(false)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); + + dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, true); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(3)); + + dispatchHandler = new EventHandlerAttribute(10, FailureLevels.Ignore, true, false); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.Ignore)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(3)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); + + dispatchHandler = new EventHandlerAttribute(10, FailureLevels.Ignore, false, true); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.Ignore)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(true)); + + dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, true, 10); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(10)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); + + dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, false, 10); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(0)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(false)); + + dispatchHandler = new EventHandlerAttribute(10, FailureLevels.ThrowAndCancel, true, 5, true); + Assert.IsTrue(dispatchHandler.Order.Equals(10)); + Assert.IsTrue(dispatchHandler.FailureLevels.Equals(FailureLevels.ThrowAndCancel)); + Assert.IsTrue(dispatchHandler.RetryTimes.Equals(5)); + Assert.IsTrue(dispatchHandler.IsCancel.Equals(true)); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs new file mode 100644 index 000000000..e37a71b1c --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ChangePasswordEventHandler.cs @@ -0,0 +1,39 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class ChangePasswordEventHandler : ISagaEventHandler +{ + private readonly ILogger? _logger; + public ChangePasswordEventHandler(ILogger? logger=null) => _logger = logger; + + [EventHandler(10, FailureLevels.ThrowAndCancel)] + public Task HandleAsync(ChangePasswordEvent @event) + { + if (@event.Content.Contains("@")) + { + throw new ArgumentException("Invalid content parameter"); + } + return Task.CompletedTask; + } + + public Task CancelAsync(ChangePasswordEvent @event) + { + if (@event.Account.Equals("mark")) + { + throw new ArgumentException("System error, please try again later"); + } + _logger?.LogInformation("cancel success"); + return Task.CompletedTask; + } + + + [EventHandler(0, FailureLevels.Ignore, IsCancel = true)] + public Task AddCancelLogs(ChangePasswordEvent @event) + { + if (@event.Account.Equals("roller")) + { + throw new ArgumentException("System error, please try again later"); + } + _logger?.LogInformation("cancel success"); + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs new file mode 100644 index 000000000..2a3e189a9 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/MarketingEventHandler.cs @@ -0,0 +1,24 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class MarketingEventHandler +{ + [EventHandler(10, true, false)] + public void Discount(ComputeEvent computeEvent) + { + var discountRate = 0.7m; + computeEvent.DiscountAmount = computeEvent.Amount * (1 - discountRate); + computeEvent.PayAmount = computeEvent.Amount * discountRate; + } + + [EventHandler(20)] + public void FullReduction(ComputeEvent computeEvent) + { + var discounts = 0; + if (computeEvent.PayAmount > 200) + { + discounts = 50; + computeEvent.DiscountAmount += discounts; + } + computeEvent.PayAmount -= discounts; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs new file mode 100644 index 000000000..5ff8ed298 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/OrderPaymentSucceededEventHandler.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class OrderPaymentSucceededEventHandler +{ + private readonly ILogger _logger; + public OrderPaymentSucceededEventHandler(ILogger logger) => _logger = logger; + + [EventHandler(10, FailureLevels.Ignore)] + public void AddTradeRecords(OrderPaymentSucceededEvent @event) + { + _logger.LogInformation("Order paid successfully, add transaction record"); + if (@event.OrderId.Length > 10) + { + throw new NotSupportedException("Wrong order number"); + } + } + + [EventHandler(10, FailureLevels = FailureLevels.Ignore, IsCancel = true)] + public void Cancel(OrderPaymentSucceededEvent @event) + { + _logger.LogInformation("Order paid successfully, rollback transaction record"); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs new file mode 100644 index 000000000..a3740fa8f --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShipOrderEventHandler.cs @@ -0,0 +1,55 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class ShipOrderEventHandler : ISagaEventHandler +{ + private int ExecCount { get; set; } + + private readonly ILogger? _logger; + + public ShipOrderEventHandler(ILogger? logger = null) + { + _logger = logger; + ExecCount = 0; + } + + [EventHandler(10, FailureLevels.ThrowAndCancel, true)] + public Task HandleAsync(ShipOrderEvent @event) + { + ExecCount++; + if (ExecCount - 1 <= 1) + { + throw new Exception("try again"); + } + + _logger?.LogInformation("update express information"); + if (@event.OrderId.Length > 8) + { + @event.Message = "the delivery failure"; + throw new Exception("the delivery failure"); + } + @event.Message = "the delivery success"; + return Task.CompletedTask; + } + + [EventHandler(10, false, true)] + public Task CancelAsync(ShipOrderEvent @event) + { + @event.Message = "the delivery failed, rolling back success"; + _logger?.LogInformation("the delivery failed, rolling back success"); + return Task.CompletedTask; + } +} + +public class ShipOrderAndNoticeHandler : IEventHandler +{ + private readonly ILogger? _logger; + public ShipOrderAndNoticeHandler(ILogger? logger = null) => _logger = logger; + + [EventHandler(20)] + public Task HandleAsync(ShipOrderEvent @event) + { + @event.Message = "the delivery and notice success"; + _logger?.LogInformation("order delivered successfully"); + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs new file mode 100644 index 000000000..dda43d5d1 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/ShoppingCardEventHandler.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class ShoppingCardEventHandler +{ + private readonly ILogger _logger; + public ShoppingCardEventHandler(ILogger logger) => _logger = logger; + + [EventHandler(FailureLevels = FailureLevels.Ignore)] + public void AddShoppingCard(AddShoppingCartEvent @event) + { + _logger.LogInformation($"add shopping card log๏ผŒGoodsId:{@event.GoodsId},Count๏ผš{@event.Count}"); + throw new ArgumentException(nameof(@event)); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs new file mode 100644 index 000000000..fffd5b263 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/TransferEventHandler.cs @@ -0,0 +1,80 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class TransferEventHandler : ISagaEventHandler +{ + private readonly List _blackAccount = new List() { "roller", "thomas" }; + + private readonly ILogger? _logger; + private readonly IEventBus _eventBus; + + public TransferEventHandler(IEventBus eventBus, ILogger? logger = null) + { + _logger = logger; + _eventBus = eventBus; + } + + [EventHandler(EnableRetry = true, RetryTimes = 3)] + public Task HandleAsync(TransferEvent @event) + { + if (_blackAccount.Contains(@event.Account)) + { + throw new NotSupportedException("System error, please try again later"); + } + _logger?.LogInformation("deduct account balance {event}", @event.ToString()); + return Task.CompletedTask; + } + + [EventHandler(EnableRetry = true, RetryTimes = 3)] + public Task CancelAsync(TransferEvent @event) + { + if (@event.Price > 1000000) + { + throw new NotSupportedException("Large transfer returns are not supported."); + } + else + { + return Task.CompletedTask; + } + } + + [EventHandler] + public async Task DeductionMoneyHandler(DeductionMoneyEvent @event) + { + // TODO: The simulated deduction is successful + + IncreaseMoneyEvent increaseMoneyEvent = new IncreaseMoneyEvent() + { + Account = @event.PayeeAccount, + TransferAccount = @event.Account, + Money = @event.Money + }; + await _eventBus.PublishAsync(increaseMoneyEvent); + } + + [EventHandler] + public Task IncreaseMoneyHandler(IncreaseMoneyEvent @event) + { + // TODO: Succeeded in simulated increase + return Task.CompletedTask; + } +} + +public class ReceiveTransferHandler +{ + private readonly List _blackAccount = new List() { "clark", "evan" }; + + private readonly ILogger _logger; + + public ReceiveTransferHandler(ILogger logger) => _logger = logger; + + [EventHandler(EnableRetry = true, RetryTimes = 3, FailureLevels = FailureLevels.ThrowAndCancel)] + public Task HandleAsync(TransferEvent @event) + { + if (_blackAccount.Contains(@event.OptAccount)) + { + throw new NotSupportedException("System error, please try again later"); + } + _logger.LogInformation("add opt account balance"); + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs new file mode 100644 index 000000000..529b52fdd --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/EventHandlers/UserEventHandler.cs @@ -0,0 +1,27 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.EventHandlers; + +public class UserEventHandler : IEventHandler +{ + [EventHandler(10, true, 1, FailureLevels = FailureLevels.ThrowAndCancel)] + public Task HandleAsync(EditUserEvent @event) + { + throw new NotSupportedException("users cannot be modified"); + } + + /// + /// This CancelHandler is not the same as EventHandler in Saga mode, so a different order can be used + /// + /// + /// + [EventHandler(20, true, 1, FailureLevels = FailureLevels.Ignore, IsCancel = true)] + public Task CancelAsync(EditUserEvent @event) + { + throw new NotSupportedException("edit users do not support cancellation"); + } + + [EventHandler(10, true)] + public void ForgotPassword(ForgotPasswordEvent @event) + { + throw new Exception("Password retrieval is not supported"); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs new file mode 100644 index 000000000..f1a805089 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddShoppingCartEvent.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record AddShoppingCartEvent : Event +{ + public string GoodsId { get; set; } + + public int Count { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs new file mode 100644 index 000000000..a9a589c02 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/AddUserEvent.cs @@ -0,0 +1,15 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +/// +/// Just event, not handler, so let's see what happens when you publish an event +/// +public record AddUserEvent : Event +{ + public string Account { get; set; } + + public string Phone { get; set; } + + public bool Gender { get; set; } + + public string Abstract { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs new file mode 100644 index 000000000..42b650e43 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ChangePasswordEvent.cs @@ -0,0 +1,12 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public class ChangePasswordEvent : IEvent +{ + public string Account { get; set; } + + public string Content { get; set; } + + public Guid Id => Guid.NewGuid(); + + public DateTime CreationTime => DateTime.UtcNow; +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs new file mode 100644 index 000000000..c5142868b --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ComputeEvent.cs @@ -0,0 +1,26 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record ComputeEvent : Event +{ + /// + /// the unit price + /// + public decimal Price { get; set; } + + public int Count { get; set; } + + /// + /// original price + /// + public decimal Amount => Price * Count; + + /// + /// preferential price + /// + public decimal DiscountAmount { get; set; } + + /// + /// actual amount paid + /// + public decimal PayAmount { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs new file mode 100644 index 000000000..0c83c8d37 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/DeductionMoneyEvent.cs @@ -0,0 +1,12 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record DeductionMoneyEvent : Event, ITransaction +{ + public IUnitOfWork? UnitOfWork { get; set; } + + public string Account { get; set; } + + public string PayeeAccount { get; set; } + + public decimal Money { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs new file mode 100644 index 000000000..ab2538c4a --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/EditUserEvent.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record EditUserEvent : Event +{ + public string UserId { get; set; } + + public string UserName { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs new file mode 100644 index 000000000..78b973da4 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ForgotPasswordEvent.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record ForgotPasswordEvent() : Event +{ + public string Account { get; set; } + + public string Email { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs new file mode 100644 index 000000000..6baf09cb4 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/IncreaseMoneyEvent.cs @@ -0,0 +1,12 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record IncreaseMoneyEvent : Event, ITransaction +{ + public IUnitOfWork? UnitOfWork { get; set; } + + public string Account { get; set; } + + public string TransferAccount { get; set; } + + public decimal Money { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs new file mode 100644 index 000000000..5321f9a22 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentFailedIntegrationEvent.cs @@ -0,0 +1,22 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public class OrderPaymentFailedIntegrationEvent : IIntegrationEvent +{ + public Guid Id { get; init; } + + public DateTime CreationTime { get; init; } + + public string Topic { get; set; } = nameof(OrderPaymentFailedIntegrationEvent); + + public IUnitOfWork? UnitOfWork { get; set; } + + public string OrderId { get; set; } + + public OrderPaymentFailedIntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } + + public OrderPaymentFailedIntegrationEvent(Guid id, DateTime creationTime) + { + this.Id = id; + this.CreationTime = creationTime; + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs new file mode 100644 index 000000000..a76ab480e --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/OrderPaymentSucceededEvent.cs @@ -0,0 +1,8 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record OrderPaymentSucceededEvent : Event +{ + public string OrderId { get; set; } + + public long Timespan { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs new file mode 100644 index 000000000..eeb1a45ea --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/ShipOrderEvent.cs @@ -0,0 +1,10 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record ShipOrderEvent : Event +{ + public string OrderId { get; set; } + + public string OrderState { get; set; } + + public string Message { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs new file mode 100644 index 000000000..ac990bdc7 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Events/TransferEvent.cs @@ -0,0 +1,10 @@ +๏ปฟnamespace Masa.Contrib.Dispatcher.Events.Tests.Events; + +public record TransferEvent : Event +{ + public string Account { get; set; } + + public string OptAccount { get; set; } + + public decimal Price { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs new file mode 100644 index 000000000..8b6f01a13 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/FeaturesTest.cs @@ -0,0 +1,278 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests; + +[TestClass] +public class FeaturesTest : TestBase +{ + private readonly IEventBus _eventBus; + public FeaturesTest() : base() + { + _eventBus = _serviceProvider.GetRequiredService(); + } + + [TestMethod] + public async Task TestMethodsReturnType() + { + await Assert.ThrowsExceptionAsync(async () => + { + try + { + ResetMemoryEventBus(typeof(AddBasketEvent).Assembly); + } + catch (Exception) + { + ResetMemoryEventBus(typeof(FeaturesTest).Assembly); + throw; + } + await Task.CompletedTask; + }); + } + + [TestMethod] + public async Task TestNotImplementationIEvent() + { + await Assert.ThrowsExceptionAsync(async () => + { + try + { + ResetMemoryEventBus(typeof(AddCatalogEvent).Assembly); + } + catch (Exception) + { + ResetMemoryEventBus(typeof(FeaturesTest).Assembly); + throw; + } + await Task.CompletedTask; + }); + } + + [TestMethod] + public async Task TestMultiParameter() + { + await Assert.ThrowsExceptionAsync(async () => + { + try + { + ResetMemoryEventBus(typeof(AddGoodsEvent).Assembly); + } + catch (Exception) + { + ResetMemoryEventBus(typeof(FeaturesTest).Assembly); + throw; + } + await Task.CompletedTask; + }); + } + + [TestMethod] + public async Task TestCorrectEventBus() + { + AddShoppingCartEvent @event = new AddShoppingCartEvent() + { + GoodsId = Guid.NewGuid().ToString(), + Count = 1 + }; + await _eventBus.PublishAsync(@event); + } + + [TestMethod] + public async Task TestNullEvent() + { + AddShoppingCartEvent? @event = null; + await Assert.ThrowsExceptionAsync(async () => await _eventBus.PublishAsync(@event!)); + } + + [DataTestMethod] + [DataRow("50", 2, "30", "70")] + [DataRow("60", 5, "140", "160")] + public async Task TestMultiHandler(string price, int count, string discountAmount, string payAmount) + { + ComputeEvent @event = new ComputeEvent() + { + Price = Convert.ToDecimal(price), + Count = count + }; + await _eventBus.PublishAsync(@event); + Assert.IsTrue(@event.DiscountAmount == Convert.ToDecimal(discountAmount) && @event.PayAmount == Convert.ToDecimal(payAmount)); + } + + [TestMethod] + public async Task TestNotParameter() + { + await Assert.ThrowsExceptionAsync(async () => + { + try + { + ResetMemoryEventBus(typeof(DeleteGoodsEvent).Assembly); + } + catch (Exception) + { + ResetMemoryEventBus(typeof(FeaturesTest).Assembly); + throw; + } + await Task.CompletedTask; + }); + } + + [TestMethod] + public async Task TestThrowException() + { + ForgotPasswordEvent @event = new ForgotPasswordEvent() + { + Account = new Random().Next(1000, 9000).ToString(), + Email = new Random().Next(100000, 9000000).ToString() + "@qq.com", + }; + await Assert.ThrowsExceptionAsync(async () => + { + await _eventBus.PublishAsync(@event); + }); + } + + [TestMethod] + public Task TestOrderLessThenZero() + { + Assert.ThrowsException(() => + { + try + { + ResetMemoryEventBus(typeof(OrderStockConfirmedEvent).Assembly); + } + catch (ArgumentOutOfRangeException) + { + try + { + ResetMemoryEventBus(typeof(FeaturesTest).Assembly); + } + catch (Exception) + { + + } + throw; + } + }); + return Task.CompletedTask; + } + + [TestMethod] + public Task TestOnlyCancelHandler() + { + Assert.ThrowsException(() => + { + try + { + ResetMemoryEventBus(typeof(OnlyCancelHandler.Tests.Events.BindPhoneNumberEvent).Assembly); + } + catch (NotSupportedException) + { + ResetMemoryEventBus(typeof(FeaturesTest).Assembly); + throw; + } + }); + return Task.CompletedTask; + } + + [TestMethod] + public async Task TestHandlerIsIgnore() + { + var @event = new OrderPaymentSucceededEvent() + { + OrderId = "123456789012", + Timespan = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + }; + await _eventBus.PublishAsync(@event); + } + + [TestMethod] + public async Task TestPublishIntegrationEvent() + { + var @event = new OrderPaymentFailedIntegrationEvent() + { + OrderId = "123456789012", + }; + await Assert.ThrowsExceptionAsync(async () => + { + await _eventBus.PublishAsync(@event); + }); + } + + [TestMethod] + public async Task TestPublishIntegrationEventAndUseUoW() + { + base.ResetMemoryEventBus(services => + { + var unitOfWork = new Mock(); + services.AddScoped(serviceProvider => unitOfWork.Object); + return services; + }, true, typeof(AssemblyResolutionTests).Assembly); + var @event = new OrderPaymentFailedIntegrationEvent() + { + OrderId = "123456789012", + }; + await Assert.ThrowsExceptionAsync(async () => + { + await _services.BuildServiceProvider().GetRequiredService().PublishAsync(@event); + }); + } + + [TestMethod] + public async Task TestTransferEventAndOpenTransaction() + { + base.ResetMemoryEventBus(services => + { + var uoW = new Mock(); + uoW.Setup(x => x.TransactionHasBegun).Returns(true); + uoW.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); + services.AddScoped(serviceProvider => uoW.Object); + return services; + }, true, typeof(AssemblyResolutionTests).Assembly); + var @event = new DeductionMoneyEvent() + { + Account = "tom", + PayeeAccount = "Jim", + Money = 100 + }; + await _services.BuildServiceProvider().GetRequiredService().PublishAsync(@event); + } + + [TestMethod] + public async Task TestCommitAsync() + { + base.ResetMemoryEventBus(services => + { + return services; + }, true, typeof(AssemblyResolutionTests).Assembly); + var @event = new DeductionMoneyEvent() + { + Account = "tom", + PayeeAccount = "Jim", + Money = 100 + }; + var serviceProvider = _services.BuildServiceProvider(); + var eventBus = serviceProvider.GetRequiredService(); + + await Assert.ThrowsExceptionAsync(async () => await eventBus.CommitAsync(default)); + } + + [TestMethod] + public async Task TestUseUoWCommitAsync() + { + var uoW = new Mock(); + base.ResetMemoryEventBus(services => + { + uoW.Setup(e => e.CommitAsync(CancellationToken.None)).Verifiable(); + services.AddScoped(serviceProvider => uoW.Object); + return services; + }, true, typeof(AssemblyResolutionTests).Assembly); + var @event = new DeductionMoneyEvent() + { + Account = "tom", + PayeeAccount = "Jim", + Money = 100 + }; + var serviceProvider = _services.BuildServiceProvider(); + var eventBus = serviceProvider.GetRequiredService(); + await eventBus.PublishAsync(@event); + + await eventBus.CommitAsync(default); + uoW.Verify(u => u.CommitAsync(default), Times.Once); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Masa.Contrib.Dispatcher.Events.Tests.csproj b/test/Masa.Contrib.Dispatcher.Events.Tests/Masa.Contrib.Dispatcher.Events.Tests.csproj new file mode 100644 index 000000000..589d40718 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Masa.Contrib.Dispatcher.Events.Tests.csproj @@ -0,0 +1,41 @@ + + + + net6.0 + false + enable + Full + enable + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs new file mode 100644 index 000000000..e1fce509e --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/Middleware/LoggingMiddleware.cs @@ -0,0 +1,14 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests.Middleware; + +public class LoggingMiddleware : IMiddleware where TEvent : notnull, IEvent +{ + private readonly ILogger>? _logger; + public LoggingMiddleware(ILogger>? logger = null) => _logger = logger; + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + var eventType = @event.GetType(); + _logger?.LogInformation("----- Handling command {FullName} ({event})", eventType.FullName, @event); + await next(); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/SagaTest.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/SagaTest.cs new file mode 100644 index 000000000..9e8e710af --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/SagaTest.cs @@ -0,0 +1,131 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests; + +[TestClass] +public class SagaTest : TestBase +{ + private readonly IEventBus _eventBus; + public SagaTest() : base() + { + _eventBus = _serviceProvider.GetRequiredService(); + } + + [DataTestMethod] + [DataRow("60040012", "success", "the delivery and notice success")] + [DataRow("601454112", "error", "the delivery failed, rolling back success")] + public async Task TestExecuteAbnormalExit(string orderId, string orderState, string result) + { + ShipOrderEvent @event = new ShipOrderEvent() + { + OrderId = orderId, + OrderState = orderState + }; + await _eventBus.PublishAsync(@event); + Assert.IsTrue(@event.Message == result); + } + + [DataTestMethod] + [DataRow("roller", "change password notcices", 0)] + [DataRow("mark", "change password notcices @", 1)] + [DataRow("roller", "change password notcices @", 0)] + [DataRow("jordan", "change password notcices @", 0)] + public async Task TestLastCancelError(string account, string content, int isError) + { + ResetMemoryEventBus(false, null!); + ChangePasswordEvent @event = new ChangePasswordEvent() + { + Account = account, + Content = content + }; + if (isError == 1) + { + await Assert.ThrowsExceptionAsync(async () => + { + await _eventBus.PublishAsync(@event); + }); + } + else + { + await _eventBus.PublishAsync(@event); + } + } + + [TestMethod] + public async Task TestEqualMethodsBySaga() + { + EditUserEvent @event = new EditUserEvent() + { + UserId = new Random().Next(10, 1000000).ToString(), + UserName = "roller" + }; + await _eventBus.PublishAsync(@event); + } + + [DataTestMethod] + [DataRow("smith", "alice", "1000", 0)] + [DataRow("roller", "alice", "1000", 1)] + [DataRow("eddie", "clark", "2000", 0)] + [DataRow("eddie", "clark", "20000000", 1)] + public async Task TestMultiHandlerBySaga(string account, string optAccount, string price, int isError) + { + TransferEvent @event = new TransferEvent() + { + Account = account, + OptAccount = optAccount, + Price = Convert.ToDecimal(price) + }; + if (isError == 1) + { + await Assert.ThrowsExceptionAsync(async () => + { + await _eventBus.PublishAsync(@event); + }); + } + else + { + await _eventBus.PublishAsync(@event); + } + } + + [TestMethod] + public async Task TestMultiOrderBySaga() + { + IEventBus? eventBus = null; + Assert.ThrowsException(() => + { + ResetMemoryEventBus(false, typeof(SagaTest).Assembly, typeof(EditCategoryEvent).Assembly); + eventBus = _serviceProvider.GetRequiredService(); + }); + EditCategoryEvent @event = new EditCategoryEvent() + { + CategoryId = new Random().Next(100, 10000).ToString(), + CategoryName = "Name" + }; + if (eventBus != null) + { + await eventBus.PublishAsync(@event); + } + ResetMemoryEventBus(false, null!); + } + + [TestMethod] + public async Task TestLessThenZeroBySaga() + { + IEventBus? eventBus = null; + Assert.ThrowsException(() => + { + ResetMemoryEventBus(false, typeof(EditGoodsEvent).Assembly); + eventBus = _serviceProvider.GetRequiredService(); + }); + EditGoodsEvent @event = new EditGoodsEvent() + { + GoodsId = new Random().Next(10, 1000).ToString(), + CategoryId = new Random().Next(100, 10000).ToString(), + GoodsName = "Name" + }; + if (eventBus != null) + { + await eventBus.PublishAsync(@event); + } + ResetMemoryEventBus(false, null!); + } +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/TestBase.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/TestBase.cs new file mode 100644 index 000000000..67c7f5658 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/TestBase.cs @@ -0,0 +1,44 @@ +namespace Masa.Contrib.Dispatcher.Events.Tests; + +[TestClass] +public class TestBase +{ + protected IServiceProvider _serviceProvider { get; private set; } + + protected IServiceCollection _services { get; private set; } + + public TestBase() : this(null) + { + + } + + public TestBase(Func? func = null) => ResetMemoryEventBus(func, false, null); + + protected void ResetMemoryEventBus(Func? func = null, bool isAddLog = true, params Assembly[]? assemblies) + { + _services = new ServiceCollection(); + if (isAddLog) + { + _services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); + } + else + { + _services.AddLogging(loggingBuilder => + { + loggingBuilder.ClearProviders(); + loggingBuilder.Services.RemoveAll(typeof(ILogger<>)); + }); + } + + _services.AddTransient(typeof(IMiddleware<>), typeof(LoggingMiddleware<>)); + func?.Invoke(_services); + _services = assemblies == null ? _services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = _defaultAssemblies) : _services.AddTestEventBus(ServiceLifetime.Scoped, options => options.Assemblies = assemblies); + _serviceProvider = _services.BuildServiceProvider(); + } + + private static Assembly[] _defaultAssemblies => new Assembly[1] { typeof(TestBase).Assembly }; + + protected void ResetMemoryEventBus(params Assembly[] assemblies) => ResetMemoryEventBus(null, true, assemblies); + + protected void ResetMemoryEventBus(bool isAddLog, params Assembly[] assemblies) => ResetMemoryEventBus(null, true, assemblies); +} diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/_Imports.cs new file mode 100644 index 000000000..b47c66104 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/_Imports.cs @@ -0,0 +1,20 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.Contrib.Dispatcher.Events.CheckMethodsParameter.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.CheckMethodsParameterNotNull.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.CheckMethodsParameterType.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.CheckMethodsType.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.Enums; +global using Masa.Contrib.Dispatcher.Events.Options; +global using Masa.Contrib.Dispatcher.Events.OrderEqualBySaga.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.OrderLessThanZeroByFeature.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.OrderLessThanZeroBySaga.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.Tests.Events; +global using Masa.Contrib.Dispatcher.Events.Tests.Middleware; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System.Reflection; diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs new file mode 100644 index 000000000..d36cae7fd --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/CreateUserEvent.cs @@ -0,0 +1,21 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.Events; + +public record CreateUserEvent : IEvent +{ + public string Name { get; set; } + + public Guid Id { get; set; } + + public DateTime CreationTime { get; set; } + + public CreateUserEvent() + { + this.Id = Guid.NewGuid(); + this.CreationTime = DateTime.UtcNow; + } + + public CreateUserEvent(string name) : this() + { + this.Name = name; + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs new file mode 100644 index 000000000..b4d42903c --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Events/RegisterUserIntegrationEvent.cs @@ -0,0 +1,20 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Tests.Events; + +public record RegisterUserIntegrationEvent : IntegrationEvent +{ + public RegisterUserIntegrationEvent() + { + + } + + public RegisterUserIntegrationEvent(Guid id, DateTime creationTime) : base(id, creationTime) + { + + } + + public string Account { get; set; } + + public string Password { get; set; } + + public override string Topic { get; set; } = nameof(RegisterUserIntegrationEvent); +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs new file mode 100644 index 000000000..c8a4acfbb --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs @@ -0,0 +1,370 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Tests; + +[TestClass] +public class IntegrationEventBusTest +{ + private Mock _options; + private Mock> _dispatcherOptions; + private Mock _daprClient; + private Mock> _logger; + private Mock _eventLog; + private Mock> _appConfig; + private Mock _eventBus; + private Mock _uoW; + + [TestInitialize] + public void Initialize() + { + _options = new(); + _options.Setup(option => option.Services).Returns(new ServiceCollection()).Verifiable(); + _dispatcherOptions = new(); + _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services)); + _daprClient = new(); + _logger = new(); + _eventLog = new(); + _eventLog.Setup(eventLog => eventLog.SaveEventAsync(It.IsAny(), null!)).Verifiable(); + _eventLog.Setup(eventLog => eventLog.MarkEventAsInProgressAsync(It.IsAny())).Verifiable(); + _eventLog.Setup(eventLog => eventLog.MarkEventAsPublishedAsync(It.IsAny())).Verifiable(); + _eventLog.Setup(eventLog => eventLog.MarkEventAsFailedAsync(It.IsAny())).Verifiable(); + _appConfig = new(); + _appConfig.Setup(appConfig => appConfig.CurrentValue).Returns(() => new AppConfig() + { + AppId = "Test" + }); + _eventBus = new(); + _uoW = new(); + _uoW.Setup(uoW => uoW.CommitAsync(default)).Verifiable(); + _uoW.Setup(uoW => uoW.Transaction).Returns(() => null!); + } + + [TestMethod] + public void TestDispatcherOption() + { + var services = new ServiceCollection(); + DispatcherOptions options; + + Assert.ThrowsException(() => + { + options = new DispatcherOptions(services) + { + Assemblies = null! + }; + }); + Assert.ThrowsException(() => + { + options = new DispatcherOptions(services) + { + Assemblies = new System.Reflection.Assembly[0] + }; + }); + options = new DispatcherOptions(services) + { + Assemblies = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } + }; + Assert.IsTrue(options.Services.Equals(services)); + var allEventTypes = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && type != typeof(IntegrationEvent) && typeof(IEvent).IsAssignableFrom(type)).ToList(); + Assert.IsTrue(options.AllEventTypes.Count == allEventTypes.Count()); + + } + + [TestMethod] + public void TestAddMultDaprEventBus() + { + _dispatcherOptions.Object.Value.UseDaprEventBus() + .UseDaprEventBus(); + var serviceProvider = _dispatcherOptions.Object.Value.Services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestAddDaprEventBus() + { + IServiceCollection services = new ServiceCollection(); + services.AddDaprEventBus(); + var serviceProvider = services.BuildServiceProvider(); + var integrationEventBus = serviceProvider.GetRequiredService(); + Assert.IsNotNull(integrationEventBus); + } + + [TestMethod] + public void TestEmptyPubSub() + { + IServiceCollection services = new ServiceCollection(); + Assert.ThrowsException(() => + { + services.AddDaprEventBus(option => + { + option.PubSubName = ""; + }); + }); + } + + [TestMethod] + public void TestAddDaprEventBusAndChangeAssemblies() + { + IServiceCollection services = new ServiceCollection(); + + services.AddDaprEventBus(option => + { + option.Assemblies = AppDomain.CurrentDomain.GetAssemblies(); + option.PubSubName = "pubsub"; + }); + var serviceProvider = services.BuildServiceProvider(); + var integrationEventBus = serviceProvider.GetRequiredService(); + Assert.IsNotNull(integrationEventBus); + } + + [TestMethod] + public void TestAddDaprEventBusAndNullServicesAsync() + { + _options.Setup(option => option.Services).Returns(() => null!); + var ex = Assert.ThrowsException(() => _options.Object.UseDaprEventBus()); + Assert.IsTrue(ex.Message == $"Value cannot be null. (Parameter '{nameof(_options.Object.Services)}')"); + } + + [TestMethod] + public async Task TestPublishIntegrationEventAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); + RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + { + Account = "lisa", + Password = "123456" + }; + _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); + await integrationEventBus.PublishAsync(@event); + + _daprClient.Verify(dapr => dapr.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); + } + + [TestMethod] + public async Task TestPublishIntegrationEventAndFailedAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); + RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + { + Account = "lisa", + Password = "123456" + }; + _eventLog.Setup(eventLog => eventLog.MarkEventAsPublishedAsync(It.IsAny())).Throws(); + _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); + await integrationEventBus.PublishAsync(@event); + + _eventLog.Verify(eventLog => eventLog.MarkEventAsInProgressAsync(@event.Id), Times.Once); + _daprClient.Verify(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); + _eventLog.Verify(eventLog => eventLog.MarkEventAsPublishedAsync(@event.Id), Times.Once); + _eventLog.Verify(eventLog => eventLog.MarkEventAsFailedAsync(@event.Id), Times.Once); + } + + [TestMethod] + public async Task TestPublishIntegrationEventAndNotUoWAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); + RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + { + Account = "lisa", + Password = "123456", + UnitOfWork = _uoW.Object + }; + _daprClient.Setup(client => client.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default)).Verifiable(); + await integrationEventBus.PublishAsync(@event); + + _daprClient.Verify(dapr => dapr.PublishEventAsync(_dispatcherOptions.Object.Value.PubSubName, @event.Topic, @event, default), Times.Once); + } + + [TestMethod] + public async Task TestPublishEventAsync() + { + _eventBus.Setup(eventBus => eventBus.PublishAsync(It.IsAny())).Verifiable(); + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); + CreateUserEvent @event = new CreateUserEvent() + { + Name = "Tom" + }; + await integrationEventBus.PublishAsync(@event); + + _eventBus.Verify(eventBus => eventBus.PublishAsync(It.IsAny()), Times.Once); + } + [TestMethod] + public async Task TestPublishEventAndNotEventBusAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + null, + _uoW.Object); + CreateUserEvent @event = new CreateUserEvent() + { + Name = "Tom" + }; + await Assert.ThrowsExceptionAsync(async () => + { + await integrationEventBus.PublishAsync(@event); + }); + } + + [TestMethod] + public async Task TestCommitAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + _uoW.Object); + + await integrationEventBus.CommitAsync(default); + _uoW.Verify(uoW => uoW.CommitAsync(default), Times.Once); + } + + [TestMethod] + public async Task TestNotUseUowCommitAsync() + { + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + null); + + await Assert.ThrowsExceptionAsync(async () => await integrationEventBus.CommitAsync()); + } + + [TestMethod] + public void TestGetAllEventTypes() + { + _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services) + { + Assemblies = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly } + }); + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + null, + null); + + Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == _dispatcherOptions.Object.Value.AllEventTypes.Count()); + } + + + [TestMethod] + public void TestUseEventBusGetAllEventTypes() + { + var defaultAssembly = new System.Reflection.Assembly[1] { typeof(IntegrationEventBusTest).Assembly }; + _dispatcherOptions.Setup(option => option.Value).Returns(() => new DispatcherOptions(_options.Object.Services) + { + Assemblies = defaultAssembly + }); + var allEventType = defaultAssembly + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && typeof(IEvent).IsAssignableFrom(type)) + .ToList(); + _eventBus.Setup(eventBus => eventBus.GetAllEventTypes()).Returns(() => allEventType).Verifiable(); + var integrationEventBus = new IntegrationEventBus( + _dispatcherOptions.Object, + _daprClient.Object, + _eventLog.Object, + _appConfig.Object, + _logger.Object, + _eventBus.Object, + null); + + Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == _dispatcherOptions.Object.Value.AllEventTypes.Count()); + Assert.IsTrue(integrationEventBus.GetAllEventTypes().Count() == allEventType.Count()); + } + + [TestMethod] + public void TestIntegrationEvent() + { + DateTime date = DateTime.UtcNow; + Guid id = Guid.NewGuid(); + RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent() + { + Account = "lisa", + Password = "123456" + }; + Assert.IsTrue(@event.CreationTime > date); + Assert.IsTrue(@event.Id != default(Guid)); + + @event = new RegisterUserIntegrationEvent(id, date) + { + Account = "lisa", + Password = "123456" + }; + Assert.IsTrue(@event.CreationTime == date); + Assert.IsTrue(@event.Id == id); + } + + public class IntegrationEventLogService : IIntegrationEventLogService + { + public Task MarkEventAsFailedAsync(Guid eventId) + { + return Task.CompletedTask; + } + + public Task DeleteExpiresAsync(DateTime expiresAt, int batchCount = 1000, CancellationToken token = new CancellationToken()) + { + throw new NotImplementedException(); + } + + public Task MarkEventAsInProgressAsync(Guid eventId) + { + return Task.CompletedTask; + } + + public Task MarkEventAsPublishedAsync(Guid eventId) + { + return Task.CompletedTask; + } + + public Task> RetrieveEventLogsFailedToPublishAsync(int retryBatchSize = 200, int maxRetryTimes = 10, int minimumRetryInterval = 60) + { + return Task.FromResult(new List().AsEnumerable()); + } + + public Task SaveEventAsync(IIntegrationEvent @event, DbTransaction transaction) + { + return Task.CompletedTask; + } + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj new file mode 100644 index 000000000..1c9bab6a1 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.csproj @@ -0,0 +1,27 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs new file mode 100644 index 000000000..ce1ebb928 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/_Imports.cs @@ -0,0 +1,16 @@ +global using Dapr.Client; +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Options; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests.Events; +global using Masa.Contrib.Dispatcher.IntegrationEvents.Tests.Events; +global using Masa.Utils.Models.Config; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System.Data.Common; diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs new file mode 100644 index 000000000..b0092707c --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Domain/Entities/User.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Domain.Entities; + +public class User +{ + public string Id { get; set; } + + public string Name { get; set; } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs new file mode 100644 index 000000000..fcbc7479e --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/IntegrationEvent.cs @@ -0,0 +1,21 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; + +public abstract record IntegrationEvent : IIntegrationEvent +{ + public Guid Id { get; init; } + + public DateTime CreationTime { get; init; } + + [JsonIgnore] + public IUnitOfWork? UnitOfWork { get; set; } + + public abstract string Topic { get; set; } + + public IntegrationEvent() : this(Guid.NewGuid(), DateTime.UtcNow) { } + + public IntegrationEvent(Guid Id, DateTime CreationTime) + { + this.Id = Id; + this.CreationTime = CreationTime; + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs new file mode 100644 index 000000000..c21debd91 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Events/OrderPaymentSucceededIntegrationEvent.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; + +internal record OrderPaymentSucceededIntegrationEvent : IntegrationEvent +{ + public string OrderId { get; set; } + + public long PaymentTime { get; set; } + + public override string Topic { get; set; } = nameof(OrderPaymentSucceededIntegrationEvent); +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs new file mode 100644 index 000000000..0356c8d9d --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Infrastructure/CustomDbContext.cs @@ -0,0 +1,11 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Infrastructure; + +internal class CustomDbContext : IntegrationEventLogContext +{ + public DbSet Users { get; set; } = null!; + + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs new file mode 100644 index 000000000..3bc2bd7aa --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogContextTest.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; + +[TestClass] +public class IntegrationEventLogContextTest : TestBase +{ + [TestMethod] + public void TestCreateDbContext() + { + var serviceProvider = CreateDefaultProvider(); + var dbContext = serviceProvider.GetRequiredService(); + var entity = dbContext.Model.GetEntityTypes().FirstOrDefault(entityType => entityType.Name == typeof(IntegrationEventLog).FullName)!; + + Assert.IsTrue(entity.GetTableName() == "IntegrationEventLog"); + var properties = entity.GetProperties().ToList(); + Assert.IsTrue(properties.Where(x => x.Name == "Id").Select(x => x.IsPrimaryKey()).FirstOrDefault()); + Assert.IsFalse(properties.Where(x => x.Name == "Id").Select(x => x.IsNullable).FirstOrDefault()); + Assert.IsFalse(properties.Where(x => x.Name == "Content").Select(x => x.IsNullable).FirstOrDefault()); + Assert.IsFalse(properties.Where(x => x.Name == "CreationTime").Select(x => x.IsNullable).FirstOrDefault()); + Assert.IsFalse(properties.Where(x => x.Name == "State").Select(x => x.IsNullable).FirstOrDefault()); + Assert.IsFalse(properties.Where(x => x.Name == "TimesSent").Select(x => x.IsNullable).FirstOrDefault()); + Assert.IsFalse(properties.Where(x => x.Name == "EventTypeName").Select(x => x.IsNullable).FirstOrDefault()); + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs new file mode 100644 index 000000000..891d7237e --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs @@ -0,0 +1,144 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; + +[TestClass] +public class IntegrationEventLogServiceTest : TestBase +{ + [TestMethod] + public async Task TestNullDbTransactionAsync() + { + DbTransaction transaction = null!; + var @event = new OrderPaymentSucceededIntegrationEvent() + { + OrderId = "1234567890123", + PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + }; + var serviceProvider = CreateDefaultProvider(); + var dbContext = serviceProvider.GetRequiredService(); + var eventLogService = serviceProvider.GetRequiredService(); + await Assert.ThrowsExceptionAsync(async () => await eventLogService.SaveEventAsync(@event, transaction)); + } + + [TestMethod] + public void TestMultUseEventLogService() + { + var serviceProvider = CreateDefaultProvider(options => + { + options.UseEventLog(dbContextOptionsBuilder => dbContextOptionsBuilder.UseSqlite(_connection)); + }); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestNullServices() + { + var options = new DispatcherOptions(null!); + Assert.ThrowsException(() => { options.UseEventLog(options => { options.UseSqlite(base._connection); }); }); + } + + [TestMethod] + public void TestNullDbContextOptionsBuilder() + { + var options = new DispatcherOptions(new ServiceCollection()); + Assert.ThrowsException(() => { options.UseEventLog(null!); }); + } + + [TestMethod] + public void TestUseCustomDbContextByNullServices() + { + var options = new DispatcherOptions(null!); + Assert.IsNull(options.Services); + Assert.ThrowsException(() => options.UseEventLog()); + } + + [TestMethod] + public void TestGenericEventLog() + { + var options = new DispatcherOptions(new ServiceCollection()); + Assert.ThrowsException(() => options.UseEventLog()); + } + + [TestMethod] + public async Task TestCustomDbContextAsync() + { + var options = new DispatcherOptions(new ServiceCollection()); + options.Services.AddMasaDbContext(options => + options.UseSqlite(_connection).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); + + var integrationEventBus = new Mock(); + integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => + AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); + options.Services.AddScoped(serviceProvider => integrationEventBus.Object); + + options.Services.AddScoped(); + + options.UseEventLog(); + + var serviceProvider = options.Services.BuildServiceProvider(); + var eventLogService = serviceProvider.GetRequiredService(); + + var @event = new OrderPaymentSucceededIntegrationEvent() + { + OrderId = "1234567890123", + PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + }; + + var dbContext = serviceProvider.GetRequiredService(); + await dbContext.Database.EnsureCreatedAsync(); + // using (var transaction = await dbContext.Database.BeginTransactionAsync()) + // { + // await eventLogService.SaveEventAsync(@event, + // Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); + // + // await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); + // } + } + + [TestMethod] + public async Task TestAddMultEventLog() + { + var options = new DispatcherOptions(new ServiceCollection()); + options.Services.AddMasaDbContext(options => options.UseSqlite(_connection)); + + var integrationEventBus = new Mock(); + integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => + AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); + options.Services.AddScoped(serviceProvider => integrationEventBus.Object); + + options.Services.AddScoped(); + + options.UseEventLog().UseEventLog(); + + var serviceProvider = options.Services.BuildServiceProvider(); + var eventLogService = serviceProvider.GetRequiredService(); + + var @event = new OrderPaymentSucceededIntegrationEvent() + { + OrderId = "1234567890123", + PaymentTime = (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + }; + + var dbContext = serviceProvider.GetRequiredService(); + await dbContext.Database.EnsureCreatedAsync(); + // using (var transaction = dbContext.Database.BeginTransaction()) + // { + // await eventLogService.SaveEventAsync(@event, + // Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(transaction)); + // + // await eventLogService.RetrieveEventLogsPendingToPublishAsync(transaction.TransactionId); + // } + } + + [TestMethod] + public void TestGetIntegrationEventLogService() + { + var services = new ServiceCollection(); + services.AddDbContext(options => options.UseSqlite(_connection)); + var serviceProvider = services.BuildServiceProvider(); + Assert.ThrowsException(() => + { + var dbContext = serviceProvider.GetServices(); + }); + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj new file mode 100644 index 000000000..55da15d84 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.csproj @@ -0,0 +1,28 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs new file mode 100644 index 000000000..2a1bcb282 --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/TestBase.cs @@ -0,0 +1,41 @@ +namespace Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests; + +public class TestBase +{ + protected readonly SqliteConnection _connection; + + protected TestBase() + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + } + + public void Dispose() + { + _connection.Close(); + } + + protected IServiceProvider CreateDefaultProvider(Action? action = null) + { + var services = new ServiceCollection(); + services.AddScoped(); + var options = new DispatcherOptions(services); + options.UseEventLog(options => options.UseSqlite(_connection)); + action?.Invoke(options); + + var integrationEventBus = new Mock(); + integrationEventBus.Setup(e => e.GetAllEventTypes()).Returns(() => AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()).Where(type => typeof(IIntegrationEvent).IsAssignableFrom(type))); + services.AddScoped(serviceProvider => integrationEventBus.Object); + return services.BuildServiceProvider(); + } +} + +public class DispatcherOptions : IDispatcherOptions +{ + public IServiceCollection Services { get; init; } + + public DispatcherOptions(IServiceCollection services) + { + this.Services = services; + } +} diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs new file mode 100644 index 000000000..27a7955bc --- /dev/null +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/_Imports.cs @@ -0,0 +1,15 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents; +global using Masa.BuildingBlocks.Dispatcher.IntegrationEvents.Logs; +global using Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Domain.Entities; +global using Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Events; +global using Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests.Infrastructure; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System.Data.Common; +global using System.Text.Json.Serialization; diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Commands/CreateProductionCommand.cs b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Commands/CreateProductionCommand.cs new file mode 100644 index 000000000..9d1641db2 --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Commands/CreateProductionCommand.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.Commands; + +public record CreateProductionCommand : Command +{ + public string Name { get; set; } + + public int Count { get; set; } +} diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CqrsTest.cs b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CqrsTest.cs new file mode 100644 index 000000000..ef3430989 --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CqrsTest.cs @@ -0,0 +1,51 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Tests; + +[TestClass] +public class CQRSTest +{ + private IServiceCollection _services; + private IServiceProvider _serviceProvider; + private IEventBus _eventBus; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + _services.AddEventBus(); + _serviceProvider = _services.BuildServiceProvider(); + _eventBus = _serviceProvider.GetRequiredService(); + } + + + [DataTestMethod] + [DataRow("")] + [DataRow("tom")] + public void TestCommand(string name) + { + var command = new CreateProductionCommand() + { + Name = name, + Count = 0 + }; + _eventBus.PublishAsync(command); + if (string.IsNullOrEmpty(name)) + { + Assert.IsTrue(command.Count == 2); + } + else + { + Assert.IsTrue(command.Count == 1); + } + } + + [TestMethod] + public void TestQuery() + { + var query = new ProductionItemQuery() + { + ProductionId = "1" + }; + _eventBus.PublishAsync(query); + Assert.IsTrue(query.Result == "Apple"); + } +} diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CreateProductionCommandHandler.cs b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CreateProductionCommandHandler.cs new file mode 100644 index 000000000..a9eb188a3 --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/CreateProductionCommandHandler.cs @@ -0,0 +1,24 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Tests; + +public class CreateProductionCommandHandler : CommandHandler +{ + [EventHandler(1, Dispatcher.Events.Enums.FailureLevels.ThrowAndCancel, false)] + public override Task HandleAsync(CreateProductionCommand @event) + { + @event.Count++; + if (string.IsNullOrEmpty(@event.Name)) + throw new ArgumentNullException(nameof(@event)); + + if (@event.Id == default(Guid) || @event.CreationTime > DateTime.UtcNow) + throw new ArgumentNullException(nameof(@event)); + + return Task.CompletedTask; + } + + [EventHandler(1)] + public override Task CancelAsync(CreateProductionCommand @event) + { + @event.Count++; + return base.CancelAsync(@event); + } +} diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.csproj b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.csproj new file mode 100644 index 000000000..0c37ab251 --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.csproj @@ -0,0 +1,27 @@ +๏ปฟ + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/ProductionQueryHandler.cs b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/ProductionQueryHandler.cs new file mode 100644 index 000000000..7ad9f8be6 --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/ProductionQueryHandler.cs @@ -0,0 +1,18 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Tests; + +public class ProductionQueryHandler : QueryHandler +{ + public override Task HandleAsync(ProductionItemQuery @event) + { + if (string.IsNullOrEmpty(@event.ProductionId)) + throw new ArgumentNullException(nameof(@event)); + + if (@event.Id == default(Guid) || @event.CreationTime > DateTime.UtcNow) + throw new ArgumentNullException(nameof(@event)); + + if (@event.ProductionId == "1") + @event.Result = "Apple"; + + return Task.CompletedTask; + } +} diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Queries/ProductionItemQuery.cs b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Queries/ProductionItemQuery.cs new file mode 100644 index 000000000..8eed02485 --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/Queries/ProductionItemQuery.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.Queries; + +public record ProductionItemQuery : Query +{ + public override string Result { get; set; } + + public string ProductionId { get; set; } +} diff --git a/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/_Imports.cs b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/_Imports.cs new file mode 100644 index 000000000..c4cf19efc --- /dev/null +++ b/test/Masa.Contrib.ReadWriteSpliting.Cqrs.Tests/_Imports.cs @@ -0,0 +1,8 @@ +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.Contrib.Dispatcher.Events; +global using Masa.Contrib.ReadWriteSpliting.Cqrs.Commands; +global using Masa.Contrib.ReadWriteSpliting.Cqrs.Queries; +global using Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.Commands; +global using Masa.Contrib.ReadWriteSpliting.Cqrs.Tests.Queries; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/Masa.Contrib.Service.MinimalAPIs.Tests/Masa.Contrib.Service.MinimalAPIs.Tests.csproj b/test/Masa.Contrib.Service.MinimalAPIs.Tests/Masa.Contrib.Service.MinimalAPIs.Tests.csproj new file mode 100644 index 000000000..a115be5e4 --- /dev/null +++ b/test/Masa.Contrib.Service.MinimalAPIs.Tests/Masa.Contrib.Service.MinimalAPIs.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs b/test/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs new file mode 100644 index 000000000..30908d79b --- /dev/null +++ b/test/Masa.Contrib.Service.MinimalAPIs.Tests/MinimalAPITest.cs @@ -0,0 +1,48 @@ +namespace Masa.Contrib.Service.MinimalAPIs.Tests; + +[TestClass] +public class MinimalAPITest +{ + private WebApplicationBuilder _builder; + + [TestInitialize] + public void Initialize() + { + _builder = WebApplication.CreateBuilder(); + } + + [TestMethod] + public void TestAddMultiServices() + { + _builder.Services.AddServices(_builder); + _builder.Services.AddServices(_builder); + var servicePrvider = _builder.Services.BuildServiceProvider(); + Assert.IsTrue(servicePrvider.GetServices>().Count() == 1); + } + + [TestMethod] + public void AddService() + { + var app = _builder.AddServices(); + Assert.IsTrue(_builder.Services.Any(service => service.ServiceType == typeof(CustomService) && service.Lifetime == ServiceLifetime.Scoped)); + + var servicePrvider = _builder.Services.BuildServiceProvider(); + var customService = servicePrvider.GetService(); + Assert.IsNotNull(customService); + + Assert.ReferenceEquals(customService.App, app); + + Assert.ReferenceEquals(customService.Services, _builder.Services); + + Assert.IsNotNull(customService.GetRequiredService()); + Assert.IsNotNull(customService.GetService()); + + Assert.IsTrue(customService.Test() == 1); + + var newCustomService = servicePrvider.CreateScope().ServiceProvider.GetService(); + Assert.IsNotNull(newCustomService); + + Assert.IsTrue(newCustomService.Test() == 1); + + } +} diff --git a/test/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs b/test/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs new file mode 100644 index 000000000..b5308e1cf --- /dev/null +++ b/test/Masa.Contrib.Service.MinimalAPIs.Tests/Services/CustomService.cs @@ -0,0 +1,13 @@ +namespace Masa.Contrib.Service.MinimalAPIs.Tests.Services; + +public class CustomService : ServiceBase +{ + private int _times = 0; + + public CustomService(IServiceCollection services) : base(services) + { + _times++; + } + + public int Test() => _times; +} diff --git a/test/Masa.Contrib.Service.MinimalAPIs.Tests/_Imports.cs b/test/Masa.Contrib.Service.MinimalAPIs.Tests/_Imports.cs new file mode 100644 index 000000000..be65cc2d6 --- /dev/null +++ b/test/Masa.Contrib.Service.MinimalAPIs.Tests/_Imports.cs @@ -0,0 +1,4 @@ +global using Masa.Contrib.Service.MinimalAPIs.Tests.Services; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/test/Masa.Contribs.Ddd.Domain.Entities.Tests/Masa.Contribs.Ddd.Domain.Entities.Tests.csproj b/test/Masa.Contribs.Ddd.Domain.Entities.Tests/Masa.Contribs.Ddd.Domain.Entities.Tests.csproj new file mode 100644 index 000000000..5ea8a41bd --- /dev/null +++ b/test/Masa.Contribs.Ddd.Domain.Entities.Tests/Masa.Contribs.Ddd.Domain.Entities.Tests.csproj @@ -0,0 +1,14 @@ +๏ปฟ + + + net6.0 + enable + enable + false + + + + + + + diff --git a/test/Masa.Contribs.Ddd.Domain.Entities.Tests/Users.cs b/test/Masa.Contribs.Ddd.Domain.Entities.Tests/Users.cs new file mode 100644 index 000000000..8d9cda0ad --- /dev/null +++ b/test/Masa.Contribs.Ddd.Domain.Entities.Tests/Users.cs @@ -0,0 +1,7 @@ +namespace Masa.Contribs.Ddd.Domain.Entities.Tests; + +public class Users : AggregateRoot +{ + public string Name { get; set; } +} + diff --git a/test/Masa.Contribs.Ddd.Domain.Entities.Tests/_Imports.cs b/test/Masa.Contribs.Ddd.Domain.Entities.Tests/_Imports.cs new file mode 100644 index 000000000..760e6cc39 --- /dev/null +++ b/test/Masa.Contribs.Ddd.Domain.Entities.Tests/_Imports.cs @@ -0,0 +1 @@ +global using Masa.BuildingBlocks.Ddd.Domain.Entities; From db729b8a2841f05d71bff9686679f4bfe7f42a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AC=BC=E8=B0=B7=E5=AD=90?= <358683537@qq.com> Date: Thu, 24 Feb 2022 19:04:13 +0800 Subject: [PATCH 08/10] chore: update readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 012f41f47..d495daf20 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [ไธญ](README.zh-CN.md) | EN -[![codecov](https://codecov.io/gh/masastack/Masa.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/Masa.Contrib) +[![codecov](https://codecov.io/gh/masastack/MASA.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/MASA.Contrib) -# Masa.Contrib +# MASA.Contrib -The purpose of Masa.Contrib is based on [Masa.BuildingBlocks](https://github.com/masastack/Masa.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [Masa Stack](https://github.com/masastack) and [Masa Labs](https://github.com/masalabs) projects. +The purpose of Masa.Contrib is based on [MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [MASA Stack](https://github.com/masastack) and [MASA Labs](https://github.com/masalabs) projects. ## Structure @@ -28,8 +28,8 @@ Masa.Contrib โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.Dapr โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF Cross-process event โ”‚ โ”œโ”€โ”€ ReadWriteSpliting -โ”‚ โ”‚ โ””โ”€โ”€ CQRS -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.ReadWriteSpliting.CQRS CQRS +โ”‚ โ”‚ โ””โ”€โ”€ Cqrs +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.ReadWriteSpliting.Cqrs Cqrs โ”‚ โ”œโ”€โ”€ Service โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Service.MinimalAPIs Best practices for [MinimalAPI] โ”œโ”€โ”€ test From 86476d4fb8416be922c7b59916664a59c8c469f6 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 24 Feb 2022 19:10:19 +0800 Subject: [PATCH 09/10] chore: change readme --- README.md | 18 +++++++++--------- README.zh-CN.md | 28 ++++++++++++++-------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d495daf20..79c10ff24 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ # MASA.Contrib -The purpose of Masa.Contrib is based on [MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [MASA Stack](https://github.com/masastack) and [MASA Labs](https://github.com/masalabs) projects. +The purpose of MASA.Contrib is based on [MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks) to provide open, community driven reusable components for building mesh applications. These components will be used by the [MASA Stack](https://github.com/masastack) and [MASA Labs](https://github.com/masalabs) projects. ## Structure ```c# -Masa.Contrib +MASA.Contrib โ”œโ”€โ”€ solution items โ”‚ โ”œโ”€โ”€ nuget.config โ”œโ”€โ”€ src @@ -20,9 +20,9 @@ Masa.Contrib โ”‚ โ”œโ”€โ”€ Data โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF Unit of work โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Data.Contracts.EF Protocol EF version -โ”‚ โ”œโ”€โ”€ DDD -โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain In-process and cross-process support -โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF +โ”‚ โ”œโ”€โ”€ Ddd +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Ddd.Domain In-process and cross-process support +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Ddd.Domain.Repository.EF โ”‚ โ”œโ”€โ”€ Dispatcher โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events In-process event โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.Dapr @@ -44,8 +44,8 @@ Masa.Contrib โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.Tests โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF.Tests โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests -โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Tests -โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Ddd.Domain.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Ddd.Domain.Repository.EF.Tests ``` ## Feature @@ -79,7 +79,7 @@ What is [MinimalAPI](https://devblogs.microsoft.com/aspnet/asp-net-core-updates- ### 3. CQRS -what is[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[Usage introduction](/src/ReadWriteSpliting/CQRS/Masa.Contrib.ReadWriteSpliting.CQRS/README.md) +what is[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[Usage introduction](/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.md) ### 4. IntegrationEventBus @@ -89,7 +89,7 @@ Realize cross-process events based on Daprใ€‚[Usage introduction](/src/Dispatche ### 5. DomainEventBus -[Usage introduction](/src/DDD/Masa.Contrib.DDD.Domain/README.md) +[Usage introduction](/src/Ddd/Masa.Contrib.Ddd.Domain/README.md) > Advantage๏ผš > diff --git a/README.zh-CN.md b/README.zh-CN.md index 3d962f1c8..40aa7223a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,15 +1,15 @@ ๏ปฟไธญ | [EN](README.md) -[![codecov](https://codecov.io/gh/masastack/Masa.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/Masa.Contrib) +[![codecov](https://codecov.io/gh/masastack/MASA.Contrib/branch/develop/graph/badge.svg?token=87TPNHUHW2)](https://codecov.io/gh/masastack/MASA.Contrib) -# Masa.Contrib +# MASA.Contrib -Masa.Contribๆ˜ฏๅŸบไบŽ[Masa.BuildingBlocks](https://github.com/masastack/Masa.BuildingBlocks)ๆไพ›ๅผ€ๆ”พ, ็คพๅŒบ้ฉฑๅŠจ็š„ๅฏ้‡็”จ็ป„ไปถ๏ผŒ็”จไบŽๆž„ๅปบ็ฝ‘ๆ ผๅบ”็”จ็จ‹ๅบใ€‚่ฟ™ไบ›็ป„ไปถๅฐ†่ขซ[Masa Stack](https://github.com/masastack)ๅ’Œ[Masa Labs](https://github.com/masalabs)็ญ‰้กน็›ฎไฝฟ็”จใ€‚ +MASA.Contribๆ˜ฏๅŸบไบŽ[MASA.BuildingBlocks](https://github.com/masastack/MASA.BuildingBlocks)ๆไพ›ๅผ€ๆ”พ, ็คพๅŒบ้ฉฑๅŠจ็š„ๅฏ้‡็”จ็ป„ไปถ๏ผŒ็”จไบŽๆž„ๅปบ็ฝ‘ๆ ผๅบ”็”จ็จ‹ๅบใ€‚่ฟ™ไบ›็ป„ไปถๅฐ†่ขซ[MaMASAsa Stack](https://github.com/masastack)ๅ’Œ[MASA Labs](https://github.com/masalabs)็ญ‰้กน็›ฎไฝฟ็”จใ€‚ ## ็ป“ๆž„ ```c# -Masa.Contrib +MASA.Contrib โ”œโ”€โ”€ solution items โ”‚ โ”œโ”€โ”€ nuget.config โ”œโ”€โ”€ src @@ -20,16 +20,16 @@ Masa.Contrib โ”‚ โ”œโ”€โ”€ Data โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF ๅทฅไฝœๅ•ๅ…ƒ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Data.Contracts.EF ่ง„็บฆEF็‰ˆ -โ”‚ โ”œโ”€โ”€ DDD -โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain ่ฟ›็จ‹ๅ†…ใ€่ทจ่ฟ›็จ‹้ƒฝๆ”ฏๆŒ -โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF +โ”‚ โ”œโ”€โ”€ Ddd +โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Ddd.Domain ่ฟ›็จ‹ๅ†…ใ€่ทจ่ฟ›็จ‹้ƒฝๆ”ฏๆŒ +โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Ddd.Domain.Repository.EF โ”‚ โ”œโ”€โ”€ Dispatcher โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events ่ฟ›็จ‹ๅ†…ไบ‹ไปถ โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.Dapr โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF ่ทจ่ฟ›็จ‹ไบ‹ไปถ โ”‚ โ”œโ”€โ”€ ReadWriteSpliting -โ”‚ โ”‚ โ””โ”€โ”€ CQRS -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.ReadWriteSpliting.CQRS CQRS +โ”‚ โ”‚ โ””โ”€โ”€ Cqrs +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.ReadWriteSpliting.Cqrs Cqrs โ”‚ โ”œโ”€โ”€ Service โ”‚ โ”‚ โ””โ”€โ”€ Masa.Contrib.Service.MinimalAPIs MinimalAPIๆœ€ไฝณๅฎž่ทต โ”œโ”€โ”€ test @@ -44,8 +44,8 @@ Masa.Contrib โ”‚ โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.Events.Tests โ”‚ โ”œโ”€โ”€ Masa.Contrib.Data.UoW.EF.Tests โ”‚ โ”œโ”€โ”€ Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests -โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Tests -โ”‚ โ”œโ”€โ”€ Masa.Contrib.DDD.Domain.Repository.EF.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Ddd.Domain.Tests +โ”‚ โ”œโ”€โ”€ Masa.Contrib.Ddd.Domain.Repository.EF.Tests ``` ## ็‰นๆ€ง @@ -79,7 +79,7 @@ Masa.Contrib ### 3. CQRS -ไป€ไนˆๆ˜ฏ[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[็”จๆณ•ไป‹็ป](/src/ReadWriteSpliting/CQRS/Masa.Contrib.ReadWriteSpliting.CQRS/README.zh-CN.md) +ไป€ไนˆๆ˜ฏ[CQRS](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)๏ผŸ[็”จๆณ•ไป‹็ป](/src/ReadWriteSpliting/Cqrs/Masa.Contrib.ReadWriteSpliting.Cqrs/README.zh-CN.md) ### 4. IntegrationEventBus @@ -89,7 +89,7 @@ Masa.Contrib ### 5. DomainEventBus -[็”จๆณ•ไป‹็ป](/src/DDD/Masa.Contrib.DDD.Domain/README.zh-CN.md) +[็”จๆณ•ไป‹็ป](/src/Ddd/Masa.Contrib.Ddd.Domain/README.zh-CN.md) > ไผ˜ๅŠฟ๏ผš > @@ -140,5 +140,5 @@ builder.Services.AddEventBus(options => { ## โ˜€๏ธ ๆŽˆๆƒๅ่ฎฎ -[![Masa.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) +[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) From c4ba50b9a876662f0620f1a583023b8f8775078a Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 24 Feb 2022 19:11:59 +0800 Subject: [PATCH 10/10] chore: change readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 79c10ff24..33870b755 100644 --- a/README.md +++ b/README.md @@ -141,4 +141,4 @@ To ensure the reliability of the entire source code, the unit test coverage is a ## โ˜€๏ธ License agreement -[![Masa.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt) +[![MASA.Contrib](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](/LICENSE.txt)