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
+[](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
-[](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/LICENSE)
+[](/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)
+[](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%
## ☀️ 授权协议
-[](http://gitlab-hz.lonsid.cn/MASA-Stack/Contribs/MASA.Contrib/-/tree/develop/LICENSE)
+[](/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 000000000..2128805c1
Binary files /dev/null and b/packageIcon.png differ
diff --git a/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiClient.cs b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiClient.cs
new file mode 100644
index 000000000..8ad400b82
--- /dev/null
+++ b/src/BasicAbility/MASA.Contrib.BasicAbility.Dcc/ConfigurationApiClient.cs
@@ -0,0 +1,158 @@
+namespace MASA.Contrib.BasicAbility.Dcc;
+
+public class ConfigurationApiClient : ConfigurationAPIBase, IConfigurationApiClient
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IMemoryCacheClient _client;
+ private readonly JsonSerializerOptions _jsonSerializerOptions;
+
+ private readonly ConcurrentDictionary>> _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