diff --git a/Masa.Contrib.sln b/Masa.Contrib.sln index 67e9e4980..391638397 100644 --- a/Masa.Contrib.sln +++ b/Masa.Contrib.sln @@ -138,15 +138,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.SearchEngine.A EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Isolation", "src\BuildingBlocks\MASA.BuildingBlocks\src\Isolation\Masa.BuildingBlocks.Isolation\Masa.BuildingBlocks.Isolation.csproj", "{B689E82B-B3E8-4C83-B56C-D4C27206AAC6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF", "src\Isolation\Masa.Contrib.Isolation.UoW.EF\Masa.Contrib.Isolation.UoW.EF.csproj", "{0C25062D-60A5-4690-974A-CDA9619866B4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation", "src\Isolation\Masa.Contrib.Isolation\Masa.Contrib.Isolation.csproj", "{F52E5BA6-C2D4-4797-AF71-6389FEC246B5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.MultiTenant", "src\Isolation\Masa.Contrib.Isolation.MultiTenant\Masa.Contrib.Isolation.MultiTenant.csproj", "{6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.MultiEnvironment", "src\Isolation\Masa.Contrib.Isolation.MultiEnvironment\Masa.Contrib.Isolation.MultiEnvironment.csproj", "{823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.Environment", "src\Isolation\Masa.Contrib.Isolation.Environment\Masa.Contrib.Isolation.Environment.csproj", "{E9A3C62C-EA85-40C3-BB3B-7836D0337F63}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.MultiTenant", "src\Isolation\Masa.Contrib.Isolation.MultiTenant\Masa.Contrib.Isolation.MultiTenant.csproj", "{7C382E43-A515-4C67-9296-FB8A630C4A49}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Tests\Masa.Contrib.Isolation.UoW.EF.Tests.csproj", "{50551732-AAE0-4F77-B401-9DD02E226CDB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF", "src\Isolation\Masa.Contrib.Isolation.UoW.EF\Masa.Contrib.Isolation.UoW.EF.csproj", "{95AF448B-F34C-4DF8-9E9B-DF499FABA099}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF.Web.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Web.Tests\Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj", "{7CF33D08-9468-464D-B52E-955A5667EE42}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.MultiEnvironment.Tests", "test\Masa.Contrib.Isolation.MultiEnvironment.Tests\Masa.Contrib.Isolation.MultiEnvironment.Tests.csproj", "{4C3307D4-2A6B-41E3-BE9E-2FD083578450}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.MultiTenant.Tests", "test\Masa.Contrib.Isolation.MultiTenant.Tests\Masa.Contrib.Isolation.MultiTenant.Tests.csproj", "{AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.Tests", "test\Masa.Contrib.Isolation.Tests\Masa.Contrib.Isolation.Tests.csproj", "{F1826D84-1A16-41A4-A57B-F0DBB5A18745}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Tests\Masa.Contrib.Isolation.UoW.EF.Tests.csproj", "{2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF.Web.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Web.Tests\Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj", "{F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -524,46 +532,78 @@ Global {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|Any CPU.Build.0 = Release|Any CPU {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|x64.ActiveCfg = Release|Any CPU {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|x64.Build.0 = Release|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|x64.ActiveCfg = Debug|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|x64.Build.0 = Debug|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|Any CPU.Build.0 = Release|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|x64.ActiveCfg = Release|Any CPU - {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|x64.Build.0 = Release|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|x64.ActiveCfg = Debug|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|x64.Build.0 = Debug|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|Any CPU.Build.0 = Release|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|x64.ActiveCfg = Release|Any CPU - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|x64.Build.0 = Release|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|x64.ActiveCfg = Debug|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|x64.Build.0 = Debug|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|Any CPU.Build.0 = Release|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|x64.ActiveCfg = Release|Any CPU - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|x64.Build.0 = Release|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|x64.ActiveCfg = Debug|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|x64.Build.0 = Debug|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|Any CPU.Build.0 = Release|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|x64.ActiveCfg = Release|Any CPU - {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|x64.Build.0 = Release|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|x64.ActiveCfg = Debug|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|x64.Build.0 = Debug|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|Any CPU.Build.0 = Release|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|x64.ActiveCfg = Release|Any CPU - {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|x64.Build.0 = Release|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Debug|x64.Build.0 = Debug|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Release|Any CPU.Build.0 = Release|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Release|x64.ActiveCfg = Release|Any CPU + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5}.Release|x64.Build.0 = Release|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Debug|x64.ActiveCfg = Debug|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Debug|x64.Build.0 = Debug|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Release|Any CPU.Build.0 = Release|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Release|x64.ActiveCfg = Release|Any CPU + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7}.Release|x64.Build.0 = Release|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Debug|x64.ActiveCfg = Debug|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Debug|x64.Build.0 = Debug|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Release|Any CPU.Build.0 = Release|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Release|x64.ActiveCfg = Release|Any CPU + {7C382E43-A515-4C67-9296-FB8A630C4A49}.Release|x64.Build.0 = Release|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Debug|x64.ActiveCfg = Debug|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Debug|x64.Build.0 = Debug|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Release|Any CPU.Build.0 = Release|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Release|x64.ActiveCfg = Release|Any CPU + {95AF448B-F34C-4DF8-9E9B-DF499FABA099}.Release|x64.Build.0 = Release|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Debug|x64.Build.0 = Debug|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Release|Any CPU.Build.0 = Release|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Release|x64.ActiveCfg = Release|Any CPU + {4C3307D4-2A6B-41E3-BE9E-2FD083578450}.Release|x64.Build.0 = Release|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Debug|x64.Build.0 = Debug|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Release|Any CPU.Build.0 = Release|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Release|x64.ActiveCfg = Release|Any CPU + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83}.Release|x64.Build.0 = Release|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Debug|x64.ActiveCfg = Debug|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Debug|x64.Build.0 = Debug|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Release|Any CPU.Build.0 = Release|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Release|x64.ActiveCfg = Release|Any CPU + {F1826D84-1A16-41A4-A57B-F0DBB5A18745}.Release|x64.Build.0 = Release|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Debug|x64.Build.0 = Debug|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Release|Any CPU.Build.0 = Release|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Release|x64.ActiveCfg = Release|Any CPU + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38}.Release|x64.Build.0 = Release|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Debug|x64.ActiveCfg = Debug|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Debug|x64.Build.0 = Debug|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Release|Any CPU.Build.0 = Release|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Release|x64.ActiveCfg = Release|Any CPU + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -632,11 +672,15 @@ Global {3F8532EF-3DC9-45F8-9562-994ABE066585} = {8C39C640-0E8A-43A7-890C-9742B6B70AA4} {31262D61-26A4-4302-968D-52B8DA4558CD} = {38E6C400-90C0-493E-9266-C1602E229F1B} {B689E82B-B3E8-4C83-B56C-D4C27206AAC6} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} - {0C25062D-60A5-4690-974A-CDA9619866B4} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} - {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} - {E9A3C62C-EA85-40C3-BB3B-7836D0337F63} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} - {50551732-AAE0-4F77-B401-9DD02E226CDB} = {38E6C400-90C0-493E-9266-C1602E229F1B} - {7CF33D08-9468-464D-B52E-955A5667EE42} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {F52E5BA6-C2D4-4797-AF71-6389FEC246B5} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {823D2A6E-20AB-45FF-AD07-5E7933FA6DE7} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {7C382E43-A515-4C67-9296-FB8A630C4A49} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {95AF448B-F34C-4DF8-9E9B-DF499FABA099} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {4C3307D4-2A6B-41E3-BE9E-2FD083578450} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {AA1993F7-5BAE-45F8-A8F5-31E88A25ED83} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {F1826D84-1A16-41A4-A57B-F0DBB5A18745} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {2E1EEC9C-37C9-4C03-A4BC-58306F8B1C38} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {F4B8D71B-DAE4-4948-A1DC-42B42DE31D4F} = {38E6C400-90C0-493E-9266-C1602E229F1B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index 0a6e41874..25a338d3d 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit 0a6e41874a315d2d46ede9873ae5d674ffbb409d +Subproject commit 25a338d3dd1e988be9f3010544a0b25a571be96f diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs deleted file mode 100644 index 10adea2b5..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Masa.Contrib.Isolation.Environment; - -public class EnvironmentSaveChangesFilter: ISaveChangesFilter -{ - private readonly IEnvironmentContext _environmentContext; - - public EnvironmentSaveChangesFilter(IEnvironmentContext environmentContext) - { - _environmentContext = environmentContext; - } - - public void OnExecuting(ChangeTracker changeTracker) - { - changeTracker.DetectChanges(); - foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) - { - if (entity.Entity is IMultiEnvironment) - { - entity.CurrentValues[nameof(IMultiEnvironment.Environment)] = _environmentContext.CurrentEnvironment; - } - } - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs deleted file mode 100644 index fed35bef8..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Masa.Contrib.Isolation.Environment; - -public static class IsolationBuilderExtensions -{ - public static IIsolationBuilder UseEnvironment(this IIsolationBuilder isolationBuilder) - { - isolationBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(EnvironmentSaveChangesFilter), ServiceLifetime.Scoped)); - isolationBuilder.Services.TryAddScoped(); - isolationBuilder.Services.TryAddScoped(typeof(IEnvironmentContext), serviceProvider => serviceProvider.GetRequiredService()); - isolationBuilder.Services.TryAddScoped(typeof(IEnvironmentSetter), serviceProvider => serviceProvider.GetRequiredService()); - return isolationBuilder; - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/EnvironmentContext.cs similarity index 82% rename from src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/EnvironmentContext.cs index 696d4491e..24265b8a2 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/EnvironmentContext.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.Environment; +namespace Masa.Contrib.Isolation.MultiEnvironment; public class EnvironmentContext : IEnvironmentContext, IEnvironmentSetter { diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/IsolationBuilderExtensions.cs new file mode 100644 index 000000000..461eb8ce1 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/IsolationBuilderExtensions.cs @@ -0,0 +1,30 @@ +namespace Masa.Contrib.Isolation.MultiEnvironment; + +public static class IsolationBuilderExtensions +{ + public const string DEFAULT_ENVIRONMENT_NAME = "ASPNETCORE_ENVIRONMENT"; + + public static IIsolationBuilder UseMultiEnvironment(this IIsolationBuilder isolationBuilder) + => isolationBuilder.UseMultiEnvironment(DEFAULT_ENVIRONMENT_NAME); + + public static IIsolationBuilder UseMultiEnvironment(this IIsolationBuilder isolationBuilder, List? parserProviders) + => isolationBuilder.UseMultiEnvironment(DEFAULT_ENVIRONMENT_NAME, parserProviders); + + public static IIsolationBuilder UseMultiEnvironment(this IIsolationBuilder isolationBuilder, string environmentName, List? parserProviders = null) + { + if (isolationBuilder.Services.Any(service => service.ImplementationType == typeof(EnvironmentProvider))) + return isolationBuilder; + + isolationBuilder.Services.AddSingleton(); + + isolationBuilder.Services.AddScoped(serviceProvider => new MultiEnvironmentMiddleware(serviceProvider, environmentName, parserProviders)); + isolationBuilder.Services.TryAddScoped(); + isolationBuilder.Services.TryAddScoped(typeof(IEnvironmentContext), serviceProvider => serviceProvider.GetRequiredService()); + isolationBuilder.Services.TryAddScoped(typeof(IEnvironmentSetter), serviceProvider => serviceProvider.GetRequiredService()); + return isolationBuilder; + } + + private class EnvironmentProvider + { + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Masa.Contrib.Isolation.MultiEnvironment.csproj similarity index 75% rename from src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj rename to src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Masa.Contrib.Isolation.MultiEnvironment.csproj index 9f22c786e..91dc691d6 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Masa.Contrib.Isolation.MultiEnvironment.csproj @@ -10,8 +10,4 @@ - - - - diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs new file mode 100644 index 000000000..79c685bf2 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/Middleware/MultiEnvironmentMiddleware.cs @@ -0,0 +1,62 @@ +namespace Masa.Contrib.Isolation.MultiEnvironment.Middleware; + +public class MultiEnvironmentMiddleware : IIsolationMiddleware +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger? _logger; + private readonly IEnumerable _parserProviders; + private readonly IEnvironmentContext _environmentContext; + private readonly IEnvironmentSetter _environmentSetter; + private readonly string _environmentKey; + private bool _handled; + + public MultiEnvironmentMiddleware(IServiceProvider serviceProvider, string environmentKey, IEnumerable? parserProviders) + { + _serviceProvider = serviceProvider; + _environmentKey = environmentKey; + _parserProviders = parserProviders ?? GetDefaultParserProviders(); + _logger = _serviceProvider.GetService>(); + _environmentContext = _serviceProvider.GetRequiredService(); + _environmentSetter = _serviceProvider.GetRequiredService(); + } + + public async Task HandleAsync() + { + if (_handled) + return; + + if (!string.IsNullOrEmpty(_environmentContext.CurrentEnvironment)) + { + _logger?.LogDebug($"The environment is successfully resolved, and the resolver is: empty"); + return; + } + + List parsers = new(); + foreach (var environmentParserProvider in _parserProviders) + { + parsers.Add(environmentParserProvider.Name); + if (await environmentParserProvider.ResolveAsync(_serviceProvider, _environmentKey, environment => _environmentSetter.SetEnvironment(environment))) + { + _logger?.LogDebug("The environment is successfully resolved, and the resolver is: {Resolvers}", string.Join("、 ", parsers)); + _handled = true; + return; + } + } + _logger?.LogDebug("Failed to resolve environment, and the resolver is: {Resolvers}", string.Join("、 ", parsers)); + _handled = true; + } + + private List GetDefaultParserProviders() + { + return new List + { + new HttpContextItemParserProvider(), + new QueryStringParserProvider(), + new FormParserProvider(), + new RouteParserProvider(), + new HeaderParserProvider(), + new CookieParserProvider(), + new EnvironmentVariablesParserProvider() + }; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md similarity index 59% rename from src/Isolation/Masa.Contrib.Isolation.Environment/README.md rename to src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md index 85bb35334..fafe1fbf0 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.md @@ -37,8 +37,8 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment()); + isolationBuilder => isolationBuilder.UseMultiEnvironment(), + dbOptions => dbOptions.UseSqlServer()); }); ```` @@ -60,7 +60,15 @@ You can also choose not to implement IMultiEnvironment when using physical isola ##### Summarize * How is the environment resolved in the controller or MinimalAPI? - * The environment provides one parser by default, and the execution order is: EnvironmentVariablesParserProvider (gets the parameters in the system environment variables, the parameters of the default environment isolation: ASPNETCORE_ENVIRONMENT) + * The environment provides 7 parsers by default, and the execution order is: HttpContextItemParserProvider、 QueryStringParserProvider、 FormParserProvider、 RouteParserProvider、 HeaderParserProvider、 CookieParserProvider、 EnvironmentVariablesParserProvider (Get the parameters in the system environment variables, the parameters of the default environment isolation: ASPNETCORE_ENVIRONMENT) + * HttpContextItemParserProvider: Get tenant information through the Items property of the requested HttpContext + * QueryStringParserProvider: Get environment information through the requested QueryString + * https://github.com/masastack?ASPNETCORE_ENVIRONMENT=dev (environment information is dev) + * FormParserProvider: Get environment information through Form form + * RouteParserProvider: Get environment information through routing + * HeaderParserProvider: Get environment information through request headers + * CookieParserProvider: Get environmental information through cookies + * EnvironmentVariablesParserProvider: Get environment information through system environment variables * If the parser fails to resolve the environment, what is the last database used? * If the parsing environment fails, return DefaultConnection directly * How to change the default environment parameter name @@ -69,8 +77,8 @@ You can also choose not to implement IMultiEnvironment when using physical isola builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetEnvironmentKey("env").UseEnvironment());// Use environment isolation + isolationBuilder => isolationBuilder.UseMultiEnvironment("env"),// Use environment isolation + dbOptions => dbOptions.UseSqlServer()); }); ```` * How to change the parser @@ -79,10 +87,10 @@ builder.Services.AddEventBus(eventBusBuilder => builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetEnvironmentParsers(new List() + isolationBuilder => isolationBuilder.UseMultiEnvironment("env", new List() { new EnvironmentVariablesParserProvider() //By default, environment information in environment isolation is obtained from system environment variables - }).UseEnvironment());// Use environment isolation + }), + dbOptions => dbOptions.UseSqlServer());// Use environment isolation }); ```` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md similarity index 61% rename from src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md rename to src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md index 72f0ffa27..ee2fc7dda 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/README.zh-CN.md @@ -38,8 +38,8 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment()); + isolationBuilder => isolationBuilder.UseMultiEnvironment(), + dbOptions => dbOptions.UseSqlServer()); }); ``` @@ -61,7 +61,15 @@ public class CustomDbContext : IsolationDbContext ##### 总结 * 控制器或MinimalAPI中环境如何解析? - * 环境默认提供了1个解析器,执行顺序为:EnvironmentVariablesParserProvider (获取系统环境变量中的参数,默认环境隔离的参数:ASPNETCORE_ENVIRONMENT) + * 环境默认提供了7个解析器,执行顺序为:HttpContextItemParserProvider、QueryStringParserProvider、FormParserProvider、RouteParserProvider、HeaderParserProvider、CookieParserProvider、EnvironmentVariablesParserProvider (获取系统环境变量中的参数,默认环境隔离的参数:ASPNETCORE_ENVIRONMENT) + * HttpContextItemParserProvider: 通过请求的HttpContext的Items属性获取租户信息 + * QueryStringParserProvider: 通过请求的QueryString获取环境信息 + * https://github.com/masastack?ASPNETCORE_ENVIRONMENT=dev (环境信息是dev) + * FormParserProvider: 通过Form表单获取环境信息 + * RouteParserProvider: 通过路由获取环境信息 + * HeaderParserProvider: 通过请求头获取环境信息 + * CookieParserProvider: 通过Cookie获取环境信息 + * EnvironmentVariablesParserProvider: 通过系统环境变量获取环境信息 * 如果解析器解析环境失败,那最后使用的数据库是? * 若解析环境失败,则直接返回DefaultConnection * 如何更改默认环境参数名 @@ -70,8 +78,8 @@ public class CustomDbContext : IsolationDbContext builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetEnvironmentKey("env").UseEnvironment());// 使用环境隔离 + isolationBuilder => isolationBuilder.UseMultiEnvironment("env"),// 使用环境隔离 + dbOptions => dbOptions.UseSqlServer()); }); ``` * 如何更改解析器 @@ -80,10 +88,10 @@ builder.Services.AddEventBus(eventBusBuilder => builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetEnvironmentParsers(new List() + isolationBuilder => isolationBuilder.UseMultiEnvironment("env", new List() { new EnvironmentVariablesParserProvider() // 默认从系统环境变量中获取环境隔离中的环境信息 - }).UseEnvironment());// 使用环境隔离 + }), + dbOptions => dbOptions.UseSqlServer());// 使用环境隔离 }); ``` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs similarity index 50% rename from src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs index aa48cf9b7..8f2a0ac59 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiEnvironment/_Imports.cs @@ -1,7 +1,8 @@ global using Masa.BuildingBlocks.Isolation; global using Masa.BuildingBlocks.Isolation.Environment; -global using Masa.Utils.Data.EntityFrameworkCore.Filters; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Masa.BuildingBlocks.Isolation.Middleware; +global using Masa.BuildingBlocks.Isolation.Parser; +global using Masa.Contrib.Isolation.MultiEnvironment.Middleware; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs index 1594b596f..6616a8b36 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs @@ -4,15 +4,7 @@ public class ConvertProvider : IConvertProvider { public object ChangeType(string value, Type conversionType) { - object result; - if (conversionType == typeof(Guid)) - { - result = Guid.Parse(value); - } - else - { - result = Convert.ChangeType(value, conversionType); - } + var result = conversionType == typeof(Guid) ? Guid.Parse(value) : Convert.ChangeType(value, conversionType); return result; } } diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs index ef6af4290..01c269090 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs @@ -1,16 +1,34 @@ -namespace Masa.Contrib.Isolation.MultiTenant; +namespace Masa.Contrib.Isolation.MultiTenant; public static class IsolationBuilderExtensions { - public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder) => isolationBuilder.UseMultiTenant(); + public const string DEFAULT_TENANT_NAME = "__tenant"; - public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder) where TKey : IComparable + public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder) + => isolationBuilder.UseMultiTenant(DEFAULT_TENANT_NAME); + + public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder, string tenantName) + => isolationBuilder.UseMultiTenant(tenantName, null); + + public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder, List? parserProviders) + => isolationBuilder.UseMultiTenant(DEFAULT_TENANT_NAME, parserProviders); + + public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder, string tenantName, List? parserProviders) { + if (isolationBuilder.Services.Any(service => service.ImplementationType == typeof(MultiTenantProvider))) + return isolationBuilder; + + isolationBuilder.Services.AddSingleton(); + + isolationBuilder.Services.AddScoped(serviceProvider => new MultiTenantMiddleware(serviceProvider, tenantName, parserProviders)); isolationBuilder.Services.TryAddSingleton(); - isolationBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(TenantSaveChangesFilter), ServiceLifetime.Scoped)); isolationBuilder.Services.TryAddScoped(); isolationBuilder.Services.TryAddScoped(typeof(ITenantContext), serviceProvider => serviceProvider.GetRequiredService()); isolationBuilder.Services.TryAddScoped(typeof(ITenantSetter), serviceProvider => serviceProvider.GetRequiredService()); return isolationBuilder; } + + private class MultiTenantProvider + { + } } diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj index 8f2f07b48..91dc691d6 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj @@ -10,8 +10,4 @@ - - - - diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs new file mode 100644 index 000000000..05b45df27 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Middleware/MultiTenantMiddleware.cs @@ -0,0 +1,61 @@ +namespace Masa.Contrib.Isolation.MultiTenant.Middleware; + +public class MultiTenantMiddleware : IIsolationMiddleware +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger? _logger; + private readonly IEnumerable _parserProviders; + private readonly ITenantContext _tenantContext; + private readonly ITenantSetter _tenantSetter; + private readonly string _tenantKey; + private bool _handled; + + public MultiTenantMiddleware(IServiceProvider serviceProvider, string tenantKey, IEnumerable? parserProviders) + { + _serviceProvider = serviceProvider; + _tenantKey = tenantKey; + _parserProviders = parserProviders ?? GetDefaultParserProviders(); + _logger = _serviceProvider.GetService>(); + _tenantContext = _serviceProvider.GetRequiredService(); + _tenantSetter = _serviceProvider.GetRequiredService(); + } + + public async Task HandleAsync() + { + if (_handled) + return; + + if (_tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) + { + _logger?.LogDebug($"The tenant is successfully resolved, and the resolver is: empty"); + return; + } + + List parsers = new(); + foreach (var tenantParserProvider in _parserProviders) + { + parsers.Add(tenantParserProvider.Name); + if (await tenantParserProvider.ResolveAsync(_serviceProvider, _tenantKey, tenantId => _tenantSetter.SetTenant(new Tenant(tenantId)))) + { + _logger?.LogDebug("The tenant is successfully resolved, and the resolver is: {Resolvers}", string.Join("、 ", parsers)); + _handled = true; + return; + } + } + _logger?.LogDebug("Failed to resolve tenant, and the resolver is: {Resolvers}", string.Join("、 ", parsers)); + _handled = true; + } + + private List GetDefaultParserProviders() + { + return new List + { + new HttpContextItemParserProvider(), + new QueryStringParserProvider(), + new FormParserProvider(), + new RouteParserProvider(), + new HeaderParserProvider(), + new CookieParserProvider() + }; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/MultiTenantOptions.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/MultiTenantOptions.cs new file mode 100644 index 000000000..f970b897a --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/MultiTenantOptions.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Isolation.MultiTenant; + +public class MultiTenantOptions +{ + public string TenantKey { get; set; } + + public List ParserProviders { get; set; } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index 9e1092506..6f94cac43 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -31,15 +31,15 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer * 1.1 When the current tenant is 00000000-0000-0000-0000-000000000002: database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; * 1.2 When the current tenant is 00000000-0000-0000-0000-000000000003: database address: server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; -* 1.3 Other tenants: database address: server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.3 Other tenants or hosts: database address: server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; 2. Using Isolation.UoW.EF ```` C# builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseMultiTenant());// Use tenant isolation + isolationBuilder => isolationBuilder.UseMultiTenant(),// Use tenant isolation + dbOptions => dbOptions.UseSqlServer()); }); ```` @@ -59,19 +59,19 @@ public class CustomDbContext : IsolationDbContext You can also choose not to implement IMultiTenant when using physical isolation > The tenant id type can be specified by yourself, the default Guid type -> * For example: Implement IMultiTenant to implement IMultiTenant, UseMultiTenant() to UseMultiTenant() +> * For example: Implement IMultiTenant to implement IMultiTenant, UseIsolationUoW() to UseIsolationUoW() ##### Summarize * How to resolve the tenant in the controller or MinimalAPI? - * The tenant provides 6 parsers by default, the execution order is: HttpContextItemTenantParserProvider, QueryStringTenantParserProvider, FormTenantParserProvider, RouteTenantParserProvider, HeaderTenantParserProvider, CookieTenantParserProvider (tenant parameter default: __tenant) - * HttpContextItemTenantParserProvider: Obtain tenant information through the Items property of the requested HttpContext - * QueryStringTenantParserProvider: Get tenant information through the requested QueryString + * The tenant provides 6 parsers by default, the execution order is: HttpContextItemParserProvider、QueryStringParserProvider、FormParserProvider、RouteParserProvider、HeaderParserProvider、CookieParserProvider (tenant parameter default: __tenant) + * HttpContextItemParserProvider: Obtain tenant information through the Items property of the requested HttpContext + * QueryStringParserProvider: Get tenant information through the requested QueryString * https://github.com/masastack?__tenant=1 (tenant id is 1) - * FormTenantParserProvider: Get tenant information through the Form form - * RouteTenantParserProvider: Get tenant information through routing - * HeaderTenantParserProvider: Get tenant information through request headers - * CookieTenantParserProvider: Get tenant information through cookies + * FormParserProvider: Get tenant information through the Form form + * RouteParserProvider: Get tenant information through routing + * HeaderParserProvider: Get tenant information through request headers + * CookieParserProvider: Get tenant information through cookies * If the resolver fails to resolve the tenant, what is the last database used? * If the resolution of the tenant fails, return the DefaultConnection directly * How to change the default tenant parameter name @@ -80,8 +80,8 @@ You can also choose not to implement IMultiTenant when using physical isolation builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenant());// Use tenant isolation + isolationBuilder => isolationBuilder.UseMultiTenant("tenant"), + dbOptions => dbOptions.UseSqlServer());// Use tenant isolation }); ```` * The default parser is not easy to use, want to change the default parser? @@ -90,10 +90,10 @@ builder.Services.AddEventBus(eventBusBuilder => builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetTenantParsers(new List() + isolationBuilder => isolationBuilder.UseMultiTenant("tenant", new List() { new QueryStringTenantParserProvider() // only use QueryStringTenantParserProvider, other parsers are removed - }).UseMultiTenant());// Use tenant isolation + }), + dbOptions => dbOptions.UseSqlServer());// Use tenant isolation }); ```` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md index 44f0c5971..1fe9c2b03 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md @@ -31,15 +31,15 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer * 1.1 当前租户为00000000-0000-0000-0000-000000000002时:数据库地址:server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; * 1.2 当前租户为00000000-0000-0000-0000-000000000003时:数据库地址:server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; -* 1.3 其他租户:数据库地址:server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.3 其他租户或宿主:数据库地址:server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; 2. 使用Isolation.UoW.EF ``` C# builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseMultiTenant());// 使用租户隔离 + isolationBuilder => isolationBuilder.UseMultiTenant(),// 使用租户隔离 + dbOptions => dbOptions.UseSqlServer()); }); ``` @@ -59,19 +59,19 @@ public class CustomDbContext : IsolationDbContext 采用物理隔离时也可以选择不实现IMultiTenant > 租户id类型支持自行指定,默认Guid类型 -> * 如:实现IMultiTenant改为实现IMultiTenant,UseMultiTenant()改为UseMultiTenant() +> * 如:实现IMultiTenant改为实现IMultiTenant,UseIsolationUoW()改为UseIsolationUoW() ##### 总结 * 控制器或MinimalAPI中租户如何解析? - * 租户默认提供了6个解析器,执行顺序分别为:HttpContextItemTenantParserProvider、QueryStringTenantParserProvider、FormTenantParserProvider、RouteTenantParserProvider、HeaderTenantParserProvider、CookieTenantParserProvider (租户参数默认:__tenant) - * HttpContextItemTenantParserProvider: 通过请求的HttpContext的Items属性获取租户信息 - * QueryStringTenantParserProvider: 通过请求的QueryString获取租户信息 + * 租户默认提供了6个解析器,执行顺序分别为:HttpContextItemParserProvider、QueryStringParserProvider、FormParserProvider、RouteParserProvider、HeaderParserProvider、CookieParserProvider (租户参数默认:__tenant) + * HttpContextItemParserProvider: 通过请求的HttpContext的Items属性获取租户信息 + * QueryStringParserProvider: 通过请求的QueryString获取租户信息 * https://github.com/masastack?__tenant=1 (租户id为1) - * FormTenantParserProvider: 通过Form表单获取租户信息 - * RouteTenantParserProvider: 通过路由获取租户信息 - * HeaderTenantParserProvider: 通过请求头获取租户信息 - * CookieTenantParserProvider: 通过Cookie获取租户信息 + * FormParserProvider: 通过Form表单获取租户信息 + * RouteParserProvider: 通过路由获取租户信息 + * HeaderParserProvider: 通过请求头获取租户信息 + * CookieParserProvider: 通过Cookie获取租户信息 * 如果解析器解析租户失败,那最后使用的数据库是? * 若解析租户失败,则直接返回DefaultConnection * 如何更改默认租户参数名 @@ -80,8 +80,8 @@ public class CustomDbContext : IsolationDbContext builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenant());// 使用租户隔离 + isolationBuilder => isolationBuilder.UseMultiTenant("tenant"),// 使用租户隔离 + dbOptions => dbOptions.UseSqlServer()); }); ``` * 默认解析器不好用,希望更改默认解析器? @@ -90,11 +90,11 @@ builder.Services.AddEventBus(eventBusBuilder => builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetTenantParsers(new List() + isolationBuilder => isolationBuilder.UseMultiTenant("tenant", new List() { new QueryStringTenantParserProvider() // 只使用QueryStringTenantParserProvider, 其它解析器移除掉 - }).UseMultiTenant());// 使用租户隔离 + }), + dbOptions => dbOptions.UseSqlServer());// 使用租户隔离 }); ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs index 595e64289..0926ce9a5 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.MultiTenant; +namespace Masa.Contrib.Isolation.MultiTenant; public class TenantContext : ITenantContext, ITenantSetter { diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs deleted file mode 100644 index c99d996db..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Masa.Contrib.Isolation.MultiTenant; - -public class TenantSaveChangesFilter : ISaveChangesFilter where TKey : IComparable -{ - private readonly ITenantContext _tenantContext; - private readonly IConvertProvider _convertProvider; - - public TenantSaveChangesFilter(ITenantContext tenantContext, IConvertProvider convertProvider) - { - _tenantContext = tenantContext; - _convertProvider = convertProvider; - } - - public void OnExecuting(ChangeTracker changeTracker) - { - changeTracker.DetectChanges(); - foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) - { - if (entity.Entity is IMultiTenant) - { - if (_tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) - { - object tenantId = _convertProvider.ChangeType(_tenantContext.CurrentTenant.Id, typeof(TKey)); - entity.CurrentValues[nameof(IMultiTenant.TenantId)] = tenantId; - } - else - { - entity.CurrentValues[nameof(IMultiTenant.TenantId)] = default(TKey); - } - } - } - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs index 7d4068925..81c6eaf9e 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs @@ -1,8 +1,9 @@ global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Middleware; global using Masa.BuildingBlocks.Isolation.MultiTenant; -global using Masa.Utils.Data.EntityFrameworkCore.Filters; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Masa.BuildingBlocks.Isolation.Parser; +global using Masa.Contrib.Isolation.MultiTenant.Middleware; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; global using System.Linq; diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs index d130c3177..d365f2a26 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs @@ -1,87 +1,13 @@ -namespace Masa.Contrib.Isolation.UoW.EF; +namespace Masa.Contrib.Isolation.UoW.EF; public class DefaultConnectionStringProvider : IConnectionStringProvider { - private readonly IUnitOfWorkAccessor _unitOfWorkAccessor; - private readonly IOptionsSnapshot _options; - private readonly IEnvironmentContext? _environmentContext; - private readonly ITenantContext? _tenantContext; - private readonly ILogger? _logger; + private readonly IIsolationDbConnectionStringProvider _isolationConnectionStringProvider; - public DefaultConnectionStringProvider( - IUnitOfWorkAccessor unitOfWorkAccessor, - IOptionsSnapshot options, - IEnvironmentContext? environmentContext = null, - ITenantContext? tenantContext = null, - ILogger? logger = null) - { - _unitOfWorkAccessor = unitOfWorkAccessor; - _options = options; - _environmentContext = environmentContext; - _tenantContext = tenantContext; - _logger = logger; - } + public DefaultConnectionStringProvider(IIsolationDbConnectionStringProvider isolationConnectionStringProvider) + => _isolationConnectionStringProvider = isolationConnectionStringProvider; - public Task GetConnectionStringAsync() => Task.FromResult(GetConnectionString()); + public Task GetConnectionStringAsync() => _isolationConnectionStringProvider.GetConnectionStringAsync(); - public string GetConnectionString() - { - if (_unitOfWorkAccessor.CurrentDbContextOptions != null) - return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; - - Expression> condition = option => true; - - if (_tenantContext != null) - { - if (_tenantContext.CurrentTenant == null) - { - _logger?.LogError($"Tenant resolution failed, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); - return SetConnectionString(); - } - - condition = condition.And(option => option.TenantId == "*" || (_tenantContext.CurrentTenant!=null && _tenantContext.CurrentTenant.Id.Equals(option.TenantId, StringComparison.CurrentCultureIgnoreCase))); - } - - if (_environmentContext != null) - { - if (string.IsNullOrEmpty(_environmentContext.CurrentEnvironment)) - { - _logger?.LogError($"Environment resolution failed, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); - return SetConnectionString(); - } - - condition = condition.And(option => option.Environment == "*" || option.Environment.Equals(_environmentContext.CurrentEnvironment, StringComparison.CurrentCultureIgnoreCase)); - } - - string connectionString; - var list = _options.Value.Isolations.Where(condition.Compile()).ToList(); - if (list.Count >= 1) - { - connectionString = list.OrderByDescending(option=>option.Score).Select(option => option.ConnectionString).FirstOrDefault()!; - if (list.Count > 1) - _logger?.LogInformation($"{GetMessage()}, Matches multiple available database link strings, the currently used ConnectionString is [{connectionString}]"); - } - else - { - connectionString = _options.Value.DefaultConnection; - _logger?.LogDebug($"{GetMessage()}, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); - } - return SetConnectionString(connectionString); - } - - private string SetConnectionString(string? connectionString = null) - { - _unitOfWorkAccessor.CurrentDbContextOptions = new MasaDbContextConfigurationOptions(connectionString ?? _options.Value.DefaultConnection); - return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; - } - - private string GetMessage() - { - List messages = new List(); - if (_environmentContext != null) - messages.Add($"Environment: [{_environmentContext.CurrentEnvironment ?? ""}]"); - if (_tenantContext != null) - messages.Add($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}]"); - return string.Join(", ", messages); - } + public string GetConnectionString() => _isolationConnectionStringProvider.GetConnectionString(); } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs index fb9eb53ac..8e829bc3a 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs @@ -4,86 +4,60 @@ public static class DispatcherOptionsExtensions { public static IEventBusBuilder UseIsolationUoW( this IEventBusBuilder eventBusBuilder, + Action isolationBuilder, Action? optionsBuilder, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + => eventBusBuilder.UseIsolationUoW(isolationBuilder, optionsBuilder, disableRollbackOnFailure, useTransaction); + + public static IEventBusBuilder UseIsolationUoW( + this IEventBusBuilder eventBusBuilder, Action isolationBuilder, + Action? optionsBuilder, bool disableRollbackOnFailure = false, bool useTransaction = true) where TDbContext : MasaDbContext + where TKey : IComparable { - eventBusBuilder.Services.UseIsolationUoW(nameof(eventBusBuilder.Services), isolationBuilder); - return eventBusBuilder.UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); + eventBusBuilder.Services.UseIsolationUoW(); + return eventBusBuilder.UseIsolation(isolationBuilder) + .UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); } public static IDispatcherOptions UseIsolationUoW( this IDispatcherOptions options, + Action isolationBuilder, Action? optionsBuilder, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + => options.UseIsolationUoW(isolationBuilder, optionsBuilder, disableRollbackOnFailure, useTransaction); + + public static IDispatcherOptions UseIsolationUoW( + this IDispatcherOptions options, Action isolationBuilder, + Action? optionsBuilder, bool disableRollbackOnFailure = false, bool useTransaction = true) where TDbContext : MasaDbContext + where TKey : IComparable { - options.Services.UseIsolationUoW(nameof(options.Services), isolationBuilder); - return options.UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); + options.Services.UseIsolationUoW(); + return options.UseIsolation(isolationBuilder) + .UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); } - private static IServiceCollection UseIsolationUoW( - this IServiceCollection services, - string paramName, - Action isolationBuilder) - { - ArgumentNullException.ThrowIfNull(services, paramName); - ArgumentNullException.ThrowIfNull(isolationBuilder); - - if (services.Any(service => service.ImplementationType == typeof(IsolationUoWProvider))) - return services; - - services.AddSingleton(); - - IsolationBuilder builder = new IsolationBuilder(services); - isolationBuilder.Invoke(builder); - - if (services.Count(service => service.ServiceType == typeof(ITenantContext) || service.ServiceType == typeof(IEnvironmentContext)) < 1) - throw new NotSupportedException("Tenant isolation and environment isolation use at least one"); - - services.Configure(option => - { - option.TenantKey = builder.TenantKey; - option.EnvironmentKey = builder.EnvironmentKey; - }); - - services.AddHttpContextAccessor(); + private static void UseIsolationUoW(this IServiceCollection services) where TKey : IComparable + => services.UseIsolationUoW() + .TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(IsolationSaveChangesFilter), ServiceLifetime.Scoped)); - if (services.Any(service => service.ServiceType == typeof(ITenantContext))) - services.AddScoped(serviceProvider => new MultiTenantMiddleware(serviceProvider, builder.TenantParsers)); - - if (services.Any(service => service.ServiceType == typeof(IEnvironmentContext))) - services.AddScoped(serviceProvider => new MultiEnvironmentMiddleware(serviceProvider, builder.EnvironmentParsers)); - - services.AddTransient(typeof(IMiddleware<>), typeof(IsolationMiddleware<>)); - services.TryAddSingleton(); - services.TryAddConfigure(Const.DEFAULT_SECTION); - services.TryAddScoped(typeof(IConnectionStringProvider), typeof(DefaultConnectionStringProvider)); - return services; - } - - private static IServiceCollection TryAddConfigure( - this IServiceCollection services, - string sectionName) - where TOptions : class + private static IServiceCollection UseIsolationUoW(this IServiceCollection services) { - IConfiguration? configuration = services.BuildServiceProvider().GetService(); - if (configuration == null) - return services; - - string name = Options.DefaultName; - services.AddOptions(); - var configurationSection = configuration.GetSection(sectionName); - services.TryAddSingleton>( - new ConfigurationChangeTokenSource(name, configurationSection)); - services.TryAddSingleton>(new NamedConfigureFromConfigurationOptions(name, - configurationSection, _ => - { - })); + if (services.Any(service => service.ServiceType == typeof(IConnectionStringProvider))) + services.Replace(new ServiceDescriptor(typeof(IConnectionStringProvider), typeof(DefaultConnectionStringProvider), ServiceLifetime.Scoped)); + else + services.TryAddScoped(); return services; } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs index b09229d28..18df12aed 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Internal; +namespace Masa.Contrib.Isolation.UoW.EF.Internal; internal static class TypeExtensions { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs deleted file mode 100644 index 26eac15d7..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF; - -public class IsolationBuilder : IIsolationBuilder -{ - public IServiceCollection Services { get; } - - public string EnvironmentKey { get; private set; } - - public string TenantKey { get; private set;} - - private List _tenantParsers; - - public IReadOnlyCollection TenantParsers => _tenantParsers; - - private List _environmentParsers; - - public IReadOnlyCollection EnvironmentParsers => _environmentParsers; - - public IsolationBuilder(IServiceCollection services) - { - Services = services; - EnvironmentKey = "ASPNETCORE_ENVIRONMENT"; - TenantKey = "__tenant"; - _tenantParsers = new List() - { - new HttpContextItemTenantParserProvider(), - new QueryStringTenantParserProvider(), - new FormTenantParserProvider(), - new RouteTenantParserProvider(), - new HeaderTenantParserProvider(), - new CookieTenantParserProvider() - }; - _environmentParsers = new List() - { - new EnvironmentVariablesParserProvider() - }; - } - - public IsolationBuilder SetEnvironmentKey(string environmentKey) - { - EnvironmentKey = environmentKey; - return this; - } - - public IsolationBuilder SetTenantKey(string tenantKey) - { - TenantKey = tenantKey; - return this; - } - - public IsolationBuilder SetTenantParsers(List tenantParserProviders) - { - _tenantParsers = tenantParserProviders; - return this; - } - - public IsolationBuilder SetEnvironmentParsers(List environmentParserProviders) - { - _environmentParsers = environmentParserProviders; - return this; - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs index 595c1e076..cc45c12da 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs @@ -30,9 +30,10 @@ public IsolationDbContext(MasaDbContextOptions options) : base(options) if (typeof(IMultiTenant<>).IsGenericInterfaceAssignableFrom(typeof(TEntity)) && _tenantContext != null) { + string defaultTenantId = default(TKey)?.ToString() ?? string.Empty; Expression> tenantFilter = entity => !IsTenantFilterEnabled || - Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiTenant.TenantId)) - .Equals(_tenantContext.CurrentTenant != null ? _tenantContext.CurrentTenant.Id : default(TKey)); + (Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() ?? string.Empty) + .Equals(_tenantContext.CurrentTenant != null ? _tenantContext.CurrentTenant.Id : defaultTenantId); expression = tenantFilter.And(expression != null, expression); } @@ -40,7 +41,7 @@ public IsolationDbContext(MasaDbContextOptions options) : base(options) { Expression> envFilter = entity => !IsEnvironmentFilterEnabled || Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiEnvironment.Environment)) - .Equals(_environmentContext != null ? _environmentContext.CurrentEnvironment : default); + .Equals(_environmentContext != null ? _environmentContext.CurrentEnvironment : default); expression = envFilter.And(expression != null, expression); } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationSaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationSaveChangesFilter.cs new file mode 100644 index 000000000..3209dbede --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationSaveChangesFilter.cs @@ -0,0 +1,41 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public class IsolationSaveChangesFilter : ISaveChangesFilter where TKey : IComparable +{ + private readonly ITenantContext? _tenantContext; + private readonly IConvertProvider? _convertProvider; + private readonly IEnvironmentContext? _environmentContext; + + public IsolationSaveChangesFilter(IServiceProvider serviceProvider) + { + _tenantContext = serviceProvider.GetService(); + _convertProvider = serviceProvider.GetService(); + _environmentContext = serviceProvider.GetService(); + } + + public void OnExecuting(ChangeTracker changeTracker) + { + changeTracker.DetectChanges(); + foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) + { + if (entity.Entity is IMultiTenant && _tenantContext != null) + { + if (_tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) + { + ArgumentNullException.ThrowIfNull(_convertProvider, nameof(_convertProvider)); + object tenantId = _convertProvider.ChangeType(_tenantContext.CurrentTenant.Id, typeof(TKey)); + entity.CurrentValues[nameof(IMultiTenant.TenantId)] = tenantId; + } + else + { + entity.CurrentValues[nameof(IMultiTenant.TenantId)] = default(TKey); + } + } + + if (entity.Entity is IMultiEnvironment && _environmentContext != null) + { + entity.CurrentValues[nameof(IMultiEnvironment.Environment)] = _environmentContext.CurrentEnvironment; + } + } + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj index 59e598ccf..d305b13a5 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj @@ -10,12 +10,12 @@ - + diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs deleted file mode 100644 index fc2398d39..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Middleware; - -public interface IIsolationMiddleware -{ - Task HandleAsync(); -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiEnvironmentMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiEnvironmentMiddleware.cs deleted file mode 100644 index 95aab59ea..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiEnvironmentMiddleware.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Middleware; - -public class MultiEnvironmentMiddleware : IIsolationMiddleware -{ - private readonly IServiceProvider _serviceProvider; - private readonly ILogger? _logger; - private readonly IEnvironmentContext _environmentContext; - private readonly IEnumerable _environmentParserProviders; - private bool _handled; - - public MultiEnvironmentMiddleware(IServiceProvider serviceProvider, IEnumerable environmentParserProviders) - { - _serviceProvider = serviceProvider; - _logger = _serviceProvider.GetService>(); - _environmentContext = _serviceProvider.GetRequiredService(); - _environmentParserProviders = environmentParserProviders; - } - - public async Task HandleAsync() - { - if(_handled) - return; - - if (!string.IsNullOrEmpty(_environmentContext.CurrentEnvironment)) - { - _logger?.LogDebug($"The environment is successfully resolved, and the resolver is: empty"); - return; - } - - List parsers = new(); - foreach (var environmentParserProvider in _environmentParserProviders) - { - parsers.Add(environmentParserProvider.Name); - if (await environmentParserProvider.ResolveAsync(_serviceProvider)) - { - _logger?.LogDebug($"The environment is successfully resolved, and the resolver is: {string.Join("、 ",parsers)}"); - _handled = true; - return; - } - } - _logger?.LogDebug($"Failed to resolve environment, and the resolver is: {string.Join("、 ",parsers)}"); - _handled = true; - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiTenantMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiTenantMiddleware.cs deleted file mode 100644 index 737664ca8..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiTenantMiddleware.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Middleware; - -public class MultiTenantMiddleware : IIsolationMiddleware -{ - private readonly IServiceProvider _serviceProvider; - private readonly ILogger? _logger; - private readonly ITenantContext _tenantContext; - private readonly IEnumerable _tenantParserProviders; - private bool _handled; - public MultiTenantMiddleware(IServiceProvider serviceProvider, IEnumerable tenantParserProviders) - { - _serviceProvider = serviceProvider; - _logger = _serviceProvider.GetService>(); - _tenantContext = _serviceProvider.GetRequiredService(); - _tenantParserProviders = tenantParserProviders; - } - - public async Task HandleAsync() - { - if(_handled) - return; - - if (_tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) - { - _logger?.LogDebug($"The tenant is successfully resolved, and the resolver is: empty"); - return; - } - - List parsers = new(); - foreach (var tenantParserProvider in _tenantParserProviders) - { - parsers.Add(tenantParserProvider.Name); - if (await tenantParserProvider.ResolveAsync(_serviceProvider)) - { - _logger?.LogDebug($"The tenant is successfully resolved, and the resolver is: {string.Join("、 ", parsers)}"); - _handled = true; - return; - } - } - _logger?.LogDebug($"Failed to resolve tenant, and the resolver is: {string.Join("、 ", parsers)}"); - _handled = true; - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs deleted file mode 100644 index 066c7ab78..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.Environment; - -public class EnvironmentVariablesParserProvider : IEnvironmentParserProvider -{ - public string Name { get; } = "EnvironmentVariables"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var environmentSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - string? environment = System.Environment.GetEnvironmentVariable(options.Value.EnvironmentKey); - if (environment != null && !string.IsNullOrEmpty(environment)) - { - environmentSetter.SetEnvironment(environment); - return Task.FromResult(true); - } - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/CookieTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/CookieTenantParserProvider.cs deleted file mode 100644 index caa46a285..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/CookieTenantParserProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - -public class CookieTenantParserProvider : ITenantParserProvider -{ - public string Name => "Cookie"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var httpContext = serviceProvider.GetService()?.HttpContext; - var tenantSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - if (httpContext?.Request.Cookies.ContainsKey(options.Value.TenantKey) ?? false) - { - var tenantId = httpContext.Request.Cookies[options.Value.TenantKey]!; - if (!string.IsNullOrEmpty(tenantId)) - { - tenantSetter.SetTenant(new Tenant(tenantId)); - return Task.FromResult(true); - } - } - - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/FormTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/FormTenantParserProvider.cs deleted file mode 100644 index 1858a7208..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/FormTenantParserProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - -public class FormTenantParserProvider : ITenantParserProvider -{ - public string Name => "Form"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var httpContext = serviceProvider.GetService()?.HttpContext; - var tenantSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - if (!(httpContext?.Request.HasFormContentType ?? false)) - return Task.FromResult(false); - - if (httpContext?.Request.Form.ContainsKey(options.Value.TenantKey) ?? false) - { - var tenantId = httpContext.Request.Form[options.Value.TenantKey].ToString(); - if (!string.IsNullOrEmpty(tenantId)) - { - tenantSetter.SetTenant(new Tenant(tenantId)); - return Task.FromResult(true); - } - } - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HeaderTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HeaderTenantParserProvider.cs deleted file mode 100644 index db129a3f5..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HeaderTenantParserProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - -public class HeaderTenantParserProvider : ITenantParserProvider -{ - public string Name => "Header"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var httpContext = serviceProvider.GetService()?.HttpContext; - var tenantSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - if (httpContext?.Request.Headers.ContainsKey(options.Value.TenantKey) ?? false) - { - var tenantId = httpContext.Request.Headers[options.Value.TenantKey].ToString(); - if (!string.IsNullOrEmpty(tenantId)) - { - tenantSetter.SetTenant(new Tenant(tenantId)); - return Task.FromResult(true); - } - } - - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HttpContextItemTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HttpContextItemTenantParserProvider.cs deleted file mode 100644 index 102f56e04..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HttpContextItemTenantParserProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - -public class HttpContextItemTenantParserProvider : ITenantParserProvider -{ - public string Name => "Items"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var httpContext = serviceProvider.GetService()?.HttpContext; - var tenantSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - if (httpContext?.Items.ContainsKey(options.Value.TenantKey) ?? false) - { - var tenantId = httpContext.Items[options.Value.TenantKey]?.ToString() ?? string.Empty; - if (!string.IsNullOrEmpty(tenantId)) - { - tenantSetter.SetTenant(new Tenant(tenantId)); - return Task.FromResult(true); - } - } - - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/QueryStringTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/QueryStringTenantParserProvider.cs deleted file mode 100644 index 9fca23fb0..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/QueryStringTenantParserProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - -public class QueryStringTenantParserProvider : ITenantParserProvider -{ - public string Name => "QueryString"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var httpContext = serviceProvider.GetService()?.HttpContext; - var tenantSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - if (httpContext?.Request.Query.ContainsKey(options.Value.TenantKey) ?? false) - { - var tenantId = httpContext.Request.Query[options.Value.TenantKey].ToString(); - if (!string.IsNullOrEmpty(tenantId)) - { - tenantSetter.SetTenant(new Tenant(tenantId)); - return Task.FromResult(true); - } - } - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/RouteTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/RouteTenantParserProvider.cs deleted file mode 100644 index 996817fde..000000000 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/RouteTenantParserProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - -public class RouteTenantParserProvider : ITenantParserProvider -{ - public string Name => "Route"; - - public Task ResolveAsync(IServiceProvider serviceProvider) - { - var httpContext = serviceProvider.GetService()?.HttpContext; - var tenantSetter = serviceProvider.GetRequiredService(); - var options = serviceProvider.GetRequiredService>(); - var tenantId = httpContext?.GetRouteValue(options.Value.TenantKey); - if (tenantId != null) - { - var tenantIdStr = tenantId.ToString(); - tenantSetter.SetTenant(new Tenant(tenantIdStr!)); - return Task.FromResult(true); - } - - return Task.FromResult(false); - } -} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md index 37af358e0..5555a9ad6 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -45,8 +45,8 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenant());// Select usage environment or tenant isolation as needed + isolationBuilder => isolationBuilder.UseMultiEnvironment().UseMultiTenant(),// Select usage environment or tenant isolation as needed + dbOptions => dbOptions.UseSqlServer()); }); ``` @@ -67,7 +67,7 @@ Tenant isolation implements IMultiTenant, and environment isolation implements I ##### Summarize * How many kinds of parser are currently supported? - * Currently two kinds of parsers are supported, one is [Environment Parser](../Masa.Contrib.Isolation.Environment/README.zh-CN.md), the other is [Tenant Parser](../Masa.Contrib .Isolation.MultiTenant/README.zh-CN.md) + * Currently two kinds of parsers are supported, one is [Environment Parser](../Masa.Contrib.Isolation.Environment/README.md), the other is [Tenant Parser](../Masa.Contrib.Isolation.MultiTenant/README.md) * How is the parser used? * After publishing events through EventBus, EventBus will automatically call the parser middleware to trigger the environment and tenant parser to parse and assign values according to the isolation usage * For scenarios where EventBus is not used, `app.UseIsolation();` needs to be added to Program.cs. After the request is initiated, it will first pass through the AspNetCore middleware provided by Isolation, and trigger the environment and tenant resolvers to parse and assign values. When the request arrives at the specified controller or Minimal method, the parsing is complete diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md index 8875d05cc..d00c516a4 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -45,8 +45,8 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( - dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenant());// 按需选择使用环境或者租户隔离 + isolationBuilder => isolationBuilder.UseMultiEnvironment().UseMultiTenant(),// 按需选择使用环境或者租户隔离 + dbOptions => dbOptions.UseSqlServer()); }); ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs index 956967146..beb8b2ab8 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs @@ -1,24 +1,14 @@ -global using Masa.BuildingBlocks.Data.UoW; -global using Masa.BuildingBlocks.Data.UoW.Options; global using Masa.BuildingBlocks.Dispatcher.Events; global using Masa.BuildingBlocks.Isolation; global using Masa.BuildingBlocks.Isolation.Environment; global using Masa.BuildingBlocks.Isolation.MultiTenant; -global using Masa.BuildingBlocks.Isolation.Options; global using Masa.Contrib.Data.UoW.EF; global using Masa.Contrib.Isolation.UoW.EF.Internal; -global using Masa.Contrib.Isolation.UoW.EF.Middleware; -global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; -global using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; global using Masa.Utils.Data.EntityFrameworkCore; -global using Microsoft.AspNetCore.Builder; -global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Routing; -global using Microsoft.Extensions.Configuration; +global using Masa.Utils.Data.EntityFrameworkCore.Filters; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.ChangeTracking; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; global using System.Linq.Expressions; global using System.Reflection; -global using System.Text; diff --git a/src/Isolation/Masa.Contrib.Isolation/DefaultIsolationConnectionStringProvider.cs b/src/Isolation/Masa.Contrib.Isolation/DefaultIsolationConnectionStringProvider.cs new file mode 100644 index 000000000..f0e945458 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/DefaultIsolationConnectionStringProvider.cs @@ -0,0 +1,85 @@ +namespace Masa.Contrib.Isolation; + +public class DefaultDbIsolationConnectionStringProvider : IIsolationDbConnectionStringProvider +{ + private readonly IUnitOfWorkAccessor _unitOfWorkAccessor; + private readonly IOptionsSnapshot _options; + private readonly IEnvironmentContext? _environmentContext; + private readonly ITenantContext? _tenantContext; + private readonly ILogger? _logger; + + public DefaultDbIsolationConnectionStringProvider( + IUnitOfWorkAccessor unitOfWorkAccessor, + IOptionsSnapshot options, + IEnvironmentContext? environmentContext = null, + ITenantContext? tenantContext = null, + ILogger? logger = null) + { + _unitOfWorkAccessor = unitOfWorkAccessor; + _options = options; + _environmentContext = environmentContext; + _tenantContext = tenantContext; + _logger = logger; + } + + public Task GetConnectionStringAsync() => Task.FromResult(GetConnectionString()); + + public string GetConnectionString() + { + if (_unitOfWorkAccessor.CurrentDbContextOptions != null) + return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; + + Expression> condition = option => true; + + if (_tenantContext != null) + { + if (_tenantContext.CurrentTenant == null) + _logger?.LogDebug($"Tenant resolution failed, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); + + condition = condition.And(option => option.TenantId == "*" || (_tenantContext.CurrentTenant != null && _tenantContext.CurrentTenant.Id.Equals(option.TenantId, StringComparison.CurrentCultureIgnoreCase))); + } + + if (_environmentContext != null) + { + if (string.IsNullOrEmpty(_environmentContext.CurrentEnvironment)) + { + _logger?.LogDebug($"Environment resolution failed, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); + } + + condition = condition.And(option + => option.Environment == "*" || option.Environment.Equals(_environmentContext.CurrentEnvironment, StringComparison.CurrentCultureIgnoreCase)); + } + + string? connectionString; + var list = _options.Value.Isolations.Where(condition.Compile()).ToList(); + if (list.Count >= 1) + { + connectionString = list.OrderByDescending(option => option.Score).Select(option => option.ConnectionString).FirstOrDefault()!; + if (list.Count > 1) + _logger?.LogInformation("{Message}, Matches multiple available database link strings, the currently used ConnectionString is [{ConnectionString}]", GetMessage(), connectionString); + } + else + { + connectionString = _options.Value.DefaultConnection; + _logger?.LogDebug("{Message}, the currently used ConnectionString is [{ConnectionString}]", GetMessage(), nameof(_options.Value.DefaultConnection)); + } + return SetConnectionString(connectionString); + } + + private string SetConnectionString(string? connectionString = null) + { + _unitOfWorkAccessor.CurrentDbContextOptions = + new MasaDbContextConfigurationOptions(connectionString ?? _options.Value.DefaultConnection); + return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; + } + + private string GetMessage() + { + List messages = new List(); + if (_environmentContext != null) + messages.Add($"Environment: [{_environmentContext.CurrentEnvironment ?? ""}]"); + if (_tenantContext != null) + messages.Add($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}]"); + return string.Join(", ", messages); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation/DispatcherOptionsExtensions.cs b/src/Isolation/Masa.Contrib.Isolation/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..4e0606053 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/DispatcherOptionsExtensions.cs @@ -0,0 +1,75 @@ +namespace Masa.Contrib.Isolation; + +public static class DispatcherOptionsExtensions +{ + /// + /// It is not recommended to use directly here, please use UseIsolationUoW + /// + /// + /// + /// + public static IEventBusBuilder UseIsolation(this IEventBusBuilder eventBusBuilder, Action isolationBuilder) + { + eventBusBuilder.Services.AddIsolation(isolationBuilder); + return eventBusBuilder; + } + + /// + /// It is not recommended to use directly here, please use UseIsolationUoW + /// + /// + /// + /// + public static IDispatcherOptions UseIsolation(this IDispatcherOptions options, Action isolationBuilder) + { + options.Services.AddIsolation(isolationBuilder); + return options; + } + + private static void AddIsolation(this IServiceCollection services, Action isolationBuilder) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(isolationBuilder); + + if (services.Any(service => service.ImplementationType == typeof(IsolationProvider))) + return; + + services.AddSingleton(); + + IsolationBuilder builder = new IsolationBuilder(services); + isolationBuilder.Invoke(builder); + + if (services.Count(service => service.ServiceType == typeof(ITenantContext) || service.ServiceType == typeof(IEnvironmentContext)) < 1) + throw new NotSupportedException("Tenant isolation and environment isolation use at least one"); + + services.AddHttpContextAccessor(); + + services + .TryAddConfigure(Const.DEFAULT_SECTION) + .AddTransient(typeof(IMiddleware<>), typeof(IsolationMiddleware<>)) + .TryAddSingleton(); + services.TryAddScoped(typeof(IIsolationDbConnectionStringProvider), typeof(DefaultDbIsolationConnectionStringProvider)); + } + + private static IServiceCollection TryAddConfigure( + this IServiceCollection services, + string sectionName) + where TOptions : class + { + IConfiguration? configuration = services.BuildServiceProvider().GetService(); + if (configuration == null) + return services; + + string name = Options.DefaultName; + services.AddOptions(); + var configurationSection = configuration.GetSection(sectionName); + services.TryAddSingleton>( + new ConfigurationChangeTokenSource(name, configurationSection)); + services.TryAddSingleton>(new NamedConfigureFromConfigurationOptions(name, configurationSection, _ => { })); + return services; + } + + private class IsolationProvider + { + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs b/src/Isolation/Masa.Contrib.Isolation/Internal/Const.cs similarity index 62% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs rename to src/Isolation/Masa.Contrib.Isolation/Internal/Const.cs index 7e66d8486..fccab93bd 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs +++ b/src/Isolation/Masa.Contrib.Isolation/Internal/Const.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Internal; +namespace Masa.Contrib.Isolation.Internal; internal class Const { diff --git a/src/Isolation/Masa.Contrib.Isolation/IsolationBuilder.cs b/src/Isolation/Masa.Contrib.Isolation/IsolationBuilder.cs new file mode 100644 index 000000000..28b5d2694 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/IsolationBuilder.cs @@ -0,0 +1,11 @@ +namespace Masa.Contrib.Isolation; + +public class IsolationBuilder : IIsolationBuilder +{ + public IServiceCollection Services { get; } + + public IsolationBuilder(IServiceCollection services) + { + Services = services; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation/IsolationBuilderExtensions.cs similarity index 86% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs rename to src/Isolation/Masa.Contrib.Isolation/IsolationBuilderExtensions.cs index 5d48710d4..5cf4da15a 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs +++ b/src/Isolation/Masa.Contrib.Isolation/IsolationBuilderExtensions.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF; +namespace Masa.Contrib.Isolation; public static class IsolationBuilderExtensions { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs b/src/Isolation/Masa.Contrib.Isolation/IsolationDbContextProvider.cs similarity index 95% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs rename to src/Isolation/Masa.Contrib.Isolation/IsolationDbContextProvider.cs index 0e44917ca..ff7aaaa4d 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation/IsolationDbContextProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF; +namespace Masa.Contrib.Isolation; public class IsolationDbContextProvider : BaseDbConnectionStringProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation/Masa.Contrib.Isolation.csproj b/src/Isolation/Masa.Contrib.Isolation/Masa.Contrib.Isolation.csproj new file mode 100644 index 000000000..5690588de --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/Masa.Contrib.Isolation.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs similarity index 94% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs rename to src/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs index 8e367c50e..8b4305883 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs +++ b/src/Isolation/Masa.Contrib.Isolation/Middleware/IsolationMiddleware.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Middleware; +namespace Masa.Contrib.Isolation.Middleware; public class IsolationMiddleware : IMiddleware where TEvent : IEvent { diff --git a/src/Isolation/Masa.Contrib.Isolation/README.md b/src/Isolation/Masa.Contrib.Isolation/README.md new file mode 100644 index 000000000..01ddc5466 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/README.md @@ -0,0 +1,5 @@ +[中](README.zh-CN.md) | EN + +## Masa.Contrib.Isolation + +The core library of Masa.Contrib.Isolation provides database address selection and parser for Isolation. It does not support stand-alone use yet. It needs to be used through Masa.Contrib.Isolation.UoW.EF. [View usage](../Masa.Contrib.Isolation.UoW.EF/README.md) \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation/README.zh-CN.md new file mode 100644 index 000000000..cee55a777 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/README.zh-CN.md @@ -0,0 +1,5 @@ +中 | [EN](README.md) + +## Masa.Contrib.Isolation + +Masa.Contrib.Isolation核心库,为Isolation提供数据库地址选择以及解析器,暂不支持单独使用,需要通过Masa.Contrib.Isolation.UoW.EF来使用,[查看用法](../Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md) \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation/_Imports.cs new file mode 100644 index 000000000..348595710 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation/_Imports.cs @@ -0,0 +1,18 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Data.UoW.Options; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; +global using Masa.BuildingBlocks.Isolation.Middleware; +global using Masa.BuildingBlocks.Isolation.MultiTenant; +global using Masa.BuildingBlocks.Isolation.Options; +global using Masa.Contrib.Isolation.Internal; +global using Masa.Contrib.Isolation.Middleware; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Http; +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 System.Linq.Expressions; diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs index 3d387ff25..7d98bc2fe 100644 --- a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccManageTest.cs @@ -1,7 +1,3 @@ -using Masa.Contrib.BasicAbility.Dcc.Internal; -using Masa.Utils.Caller.Core; -using System.Net; - namespace Masa.Contrib.BasicAbility.Dcc.Tests; [TestClass] diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs index 9c257a4fc..d14c68aaa 100644 --- a/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/DccTest.cs @@ -1,11 +1,3 @@ -using Masa.Utils.Caching.Core.Interfaces; -using Masa.Utils.Caching.Core.Models; -using Masa.Utils.Caching.DistributedMemory.Models; -using Masa.Utils.Caller.Core; -using Masa.Utils.Caller.HttpClient; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; - namespace Masa.Contrib.BasicAbility.Dcc.Tests; [TestClass] diff --git a/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs b/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs index 70394c661..81a385171 100644 --- a/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs +++ b/test/Masa.Contrib.BasicAbility.Dcc.Tests/_Imports.cs @@ -1,14 +1,25 @@ global using Masa.BuildingBlocks.Configuration; +global using Masa.Contrib.BasicAbility.Dcc.Internal; global using Masa.Contrib.BasicAbility.Dcc.Options; global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal; global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Common; global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Config; global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Enum; global using Masa.Contrib.BasicAbility.Dcc.Tests.Internal.Model; +global using Masa.Utils.Caching.Core.Interfaces; +global using Masa.Utils.Caching.Core.Models; global using Masa.Utils.Caching.DistributedMemory; global using Masa.Utils.Caching.DistributedMemory.Interfaces; +global using Masa.Utils.Caching.DistributedMemory.Models; +global using Masa.Utils.Caller.Core; +global using Masa.Utils.Caller.HttpClient; +global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; +global using System.Net; global using System.Text.Json; + + diff --git a/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/Masa.Contrib.Isolation.MultiEnvironment.Tests.csproj b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/Masa.Contrib.Isolation.MultiEnvironment.Tests.csproj new file mode 100644 index 000000000..17300f5c0 --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/Masa.Contrib.Isolation.MultiEnvironment.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/TestEnvironment.cs b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/TestEnvironment.cs new file mode 100644 index 000000000..8714d44fe --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/TestEnvironment.cs @@ -0,0 +1,70 @@ +using Masa.BuildingBlocks.Isolation.Middleware; + +namespace Masa.Contrib.Isolation.MultiEnvironment.Tests; + +[TestClass] +public class TestEnvironment +{ + [TestMethod] + public void TestSetEnvironment() + { + var services = new ServiceCollection(); + Mock isolationBuilder = new(); + isolationBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + isolationBuilder.Object.UseMultiEnvironment(); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(string.IsNullOrEmpty(serviceProvider.GetRequiredService().CurrentEnvironment)); + + serviceProvider.GetRequiredService().SetEnvironment("dev"); + Assert.IsTrue(serviceProvider.GetRequiredService().CurrentEnvironment == "dev"); + } + + [TestMethod] + public void TestUseMultiEnvironment() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + options.Object.UseMultiEnvironment(); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetService() != null); + } + + [TestMethod] + public void TestUseMultiEnvironment2() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + options.Object.UseMultiEnvironment().UseMultiEnvironment(); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetService() != null); + } + + [TestMethod] + public void TestUseMultiEnvironment3() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + options.Object.UseMultiEnvironment(new List() { }); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetService() != null); + Assert.IsTrue(serviceProvider.GetService() != null); + } +} diff --git a/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/TestMiddleware.cs b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/TestMiddleware.cs new file mode 100644 index 000000000..6d5cb7450 --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/TestMiddleware.cs @@ -0,0 +1,146 @@ +using Microsoft.Extensions.Logging.Abstractions; + +namespace Masa.Contrib.Isolation.MultiEnvironment.Tests; + +[TestClass] +public class TestMiddleware +{ + [TestMethod] + public async Task TestMultiEnvironmentMiddlewareAsync() + { + var services = new ServiceCollection(); + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("").Verifiable(); + services.AddScoped(_ => environmentContext.Object); + + Mock environmentSetter = new(); + environmentSetter.Setup(context => context.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + List parserProviders = new List + { + parserProvider.Object + }; + string environmentKey = "env"; + var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify( + provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + } + + [TestMethod] + public async Task TestMultiEnvironmentMiddleware2Async() + { + var services = new ServiceCollection(); + services.AddLogging(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("").Verifiable(); + services.AddScoped(_ => environmentContext.Object); + + Mock environmentSetter = new(); + environmentSetter.Setup(context => context.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())).Verifiable(); + List parserProviders = new List + { + parserProvider.Object + }; + string environmentKey = "env"; + var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + } + + [TestMethod] + public async Task TestMultiEnvironmentMiddleware3Async() + { + var services = new ServiceCollection(); + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("dev").Verifiable(); + services.AddScoped(_ => environmentContext.Object); + + Mock environmentSetter = new(); + environmentSetter.Setup(context => context.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + List parserProviders = new List + { + parserProvider.Object + }; + string environmentKey = "env"; + var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify( + provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + } + + [TestMethod] + public async Task TestMultiEnvironmentMiddleware4Async() + { + var services = new ServiceCollection(); + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("").Verifiable(); + services.AddScoped(_ => environmentContext.Object); + + Mock environmentSetter = new(); + environmentSetter.Setup(context => context.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + + services.AddHttpContextAccessor(); + string environmentKey = "env"; + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { environmentKey, "dev" } + } + } + }; + var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, null); + await middleware.HandleAsync(); + environmentSetter.Verify(setter => setter.SetEnvironment(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestMultiEnvironmentMiddleware5Async() + { + var services = new ServiceCollection(); + services.AddLogging(); + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("").Verifiable(); + services.AddScoped(_ => environmentContext.Object); + + Mock environmentSetter = new(); + environmentSetter.Setup(context => context.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + + services.AddHttpContextAccessor(); + string environmentKey = "env"; + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { environmentKey, "dev" } + } + } + }; + var middleware = new MultiEnvironmentMiddleware(services.BuildServiceProvider(), environmentKey, null); + await middleware.HandleAsync(); + environmentSetter.Verify(setter => setter.SetEnvironment(It.IsAny()), Times.Once); + } +} diff --git a/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/_Imports.cs new file mode 100644 index 000000000..9a5851e91 --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiEnvironment.Tests/_Imports.cs @@ -0,0 +1,8 @@ +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; +global using Masa.Contrib.Isolation.MultiEnvironment.Middleware; +global using Microsoft.AspNetCore.Http; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System; diff --git a/test/Masa.Contrib.Isolation.MultiTenant.Tests/Masa.Contrib.Isolation.MultiTenant.Tests.csproj b/test/Masa.Contrib.Isolation.MultiTenant.Tests/Masa.Contrib.Isolation.MultiTenant.Tests.csproj new file mode 100644 index 000000000..d817eea40 --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiTenant.Tests/Masa.Contrib.Isolation.MultiTenant.Tests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Isolation.MultiTenant.Tests/TestMiddleware.cs b/test/Masa.Contrib.Isolation.MultiTenant.Tests/TestMiddleware.cs new file mode 100644 index 000000000..6299cad6d --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiTenant.Tests/TestMiddleware.cs @@ -0,0 +1,356 @@ +namespace Masa.Contrib.Isolation.MultiTenant.Tests; + +[TestClass] +public class TestMiddleware +{ + [TestMethod] + public async Task TestMultiTenantMiddlewareAsync() + { + var services = new ServiceCollection(); + Mock tenantContext = new(); + Tenant tenant = null!; + tenantContext.Setup(context => context.CurrentTenant).Returns(tenant).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + List parserProviders = new List + { + parserProvider.Object + }; + string tenantKey = "tenant"; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware2Async() + { + var services = new ServiceCollection(); + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("1")).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + List parserProviders = new List + { + parserProvider.Object + }; + string tenantKey = "tenant"; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware3Async() + { + var services = new ServiceCollection(); + Mock tenantContext = new(); + Tenant tenant = null!; + tenantContext.Setup(context => context.CurrentTenant).Returns(tenant).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { tenantKey, "1" } + } + } + }; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, null); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware4Async() + { + var services = new ServiceCollection(); + Mock isolationBuilder = new(); + isolationBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + string tenantKey = "tenant"; + + isolationBuilder.Object.UseMultiTenant(tenantKey); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { tenantKey, "1" } + } + } + }; + + var middleware = services.BuildServiceProvider().GetRequiredService(); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware5Async() + { + var services = new ServiceCollection(); + Mock isolationBuilder = new(); + isolationBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + isolationBuilder.Object.UseMultiTenant(); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { "__tenant", "1" } + } + } + }; + + var middleware = services.BuildServiceProvider().GetRequiredService(); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware6Async() + { + var services = new ServiceCollection(); + Mock isolationBuilder = new(); + isolationBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + isolationBuilder.Object.UseMultiTenant(new List()); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { "__tenant", "1" } + } + } + }; + + var middleware = services.BuildServiceProvider().GetRequiredService(); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware7Async() + { + var services = new ServiceCollection(); + services.AddLogging(); + Mock tenantContext = new(); + Tenant tenant = null!; + tenantContext.Setup(context => context.CurrentTenant).Returns(tenant).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + List parserProviders = new List + { + parserProvider.Object + }; + string tenantKey = "tenant"; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware8Async() + { + var services = new ServiceCollection(); + Mock tenantContext = new(); + Tenant tenant = null!; + tenantContext.Setup(context => context.CurrentTenant).Returns(tenant).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { tenantKey, "1" } + } + } + }; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, null); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware9Async() + { + var services = new ServiceCollection(); + services.AddLogging(); + Mock isolationBuilder = new(); + isolationBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + isolationBuilder.Object.UseMultiTenant(new List()); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { "__tenant", "1" } + } + } + }; + + var middleware = services.BuildServiceProvider().GetRequiredService(); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware10Async() + { + var services = new ServiceCollection(); + services.AddLogging(); + Mock tenantContext = new(); + Tenant tenant = null!; + tenantContext.Setup(context => context.CurrentTenant).Returns(tenant).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var httpContextAccessor = services.BuildServiceProvider().GetRequiredService()!; + httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { tenantKey, "1" } + } + } + }; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, null); + await middleware.HandleAsync(); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestMultiTenantMiddleware11Async() + { + var services = new ServiceCollection(); + services.AddLogging(); + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("1")).Verifiable(); + services.AddScoped(_ => tenantContext.Object); + + Mock tenantSetter = new(); + tenantSetter.Setup(context => context.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + + Mock parserProvider = new(); + parserProvider.Setup(provider + => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>())); + List parserProviders = new List + { + parserProvider.Object + }; + string tenantKey = "tenant"; + var middleware = new MultiTenantMiddleware(services.BuildServiceProvider(), tenantKey, parserProviders); + await middleware.HandleAsync(); + parserProvider.Verify(provider => provider.ResolveAsync(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + } +} diff --git a/test/Masa.Contrib.Isolation.MultiTenant.Tests/TestTenant.cs b/test/Masa.Contrib.Isolation.MultiTenant.Tests/TestTenant.cs new file mode 100644 index 000000000..70746ff17 --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiTenant.Tests/TestTenant.cs @@ -0,0 +1,53 @@ +namespace Masa.Contrib.Isolation.MultiTenant.Tests; + +[TestClass] +public class TestEnvironment +{ + [TestMethod] + public void TestSetTenant() + { + var services = new ServiceCollection(); + Mock isolationBuilder = new(); + isolationBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + isolationBuilder.Object.UseMultiTenant(); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetRequiredService().CurrentTenant == null); + + var tenant = new Tenant("1"); + serviceProvider.GetRequiredService().SetTenant(tenant); + Assert.IsTrue(serviceProvider.GetRequiredService().CurrentTenant == tenant); + } + + [TestMethod] + public void TestChangeType() + { + var convertProvider = new ConvertProvider(); + object result = convertProvider.ChangeType("1", typeof(int)); + Assert.IsTrue(result.Equals(1)); + + var guid = Guid.NewGuid(); + result = convertProvider.ChangeType(guid.ToString(), typeof(Guid)); + Assert.IsTrue(result.Equals(guid)); + + var str = "dev"; + result = convertProvider.ChangeType(str, typeof(string)); + Assert.IsTrue(result.Equals(str)); + + result = convertProvider.ChangeType("1.1", typeof(decimal)); + Assert.IsTrue(result.Equals((decimal)1.1)); + + result = convertProvider.ChangeType("1.2", typeof(float)); + Assert.IsTrue(result.Equals((float)1.2)); + + result = convertProvider.ChangeType("1.3", typeof(double)); + Assert.IsTrue(result.Equals(1.3d)); + + result = convertProvider.ChangeType("1", typeof(ushort)); + Assert.IsTrue(result.Equals((ushort)1)); + + bool isProduction = true; + result = convertProvider.ChangeType(isProduction.ToString(), typeof(bool)); + Assert.IsTrue(result.Equals(isProduction)); + } +} diff --git a/test/Masa.Contrib.Isolation.MultiTenant.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.MultiTenant.Tests/_Imports.cs new file mode 100644 index 000000000..d9f57be74 --- /dev/null +++ b/test/Masa.Contrib.Isolation.MultiTenant.Tests/_Imports.cs @@ -0,0 +1,9 @@ +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Middleware; +global using Masa.BuildingBlocks.Isolation.MultiTenant; +global using Masa.Contrib.Isolation.MultiTenant.Middleware; +global using Microsoft.AspNetCore.Http; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System; diff --git a/test/Masa.Contrib.Isolation.Tests/Masa.Contrib.Isolation.Tests.csproj b/test/Masa.Contrib.Isolation.Tests/Masa.Contrib.Isolation.Tests.csproj new file mode 100644 index 000000000..5a4091520 --- /dev/null +++ b/test/Masa.Contrib.Isolation.Tests/Masa.Contrib.Isolation.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs b/test/Masa.Contrib.Isolation.Tests/RequestCookieCollection.cs similarity index 74% rename from test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs rename to test/Masa.Contrib.Isolation.Tests/RequestCookieCollection.cs index 16401caf8..dddc0179c 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs +++ b/test/Masa.Contrib.Isolation.Tests/RequestCookieCollection.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Tests; +namespace Masa.Contrib.Isolation.Tests; public class RequestCookieCollection : Dictionary, IRequestCookieCollection { diff --git a/test/Masa.Contrib.Isolation.Tests/TestDbIsolationConnectionStringProvider.cs b/test/Masa.Contrib.Isolation.Tests/TestDbIsolationConnectionStringProvider.cs new file mode 100644 index 000000000..d934c3adc --- /dev/null +++ b/test/Masa.Contrib.Isolation.Tests/TestDbIsolationConnectionStringProvider.cs @@ -0,0 +1,492 @@ +namespace Masa.Contrib.Isolation.Tests; + +[TestClass] +public class TestDbIsolationConnectionStringProvider +{ + private IServiceCollection _services; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + _services.AddScoped(); + } + + [TestMethod] + public async Task TestGetConnectionStringAsync() + { + string defaultConnectionString = "data source=test1;"; + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + unitOfWorkAccessor.CurrentDbContextOptions = new MasaDbContextConfigurationOptions(defaultConnectionString); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, null!); + Assert.IsTrue(await provider.GetConnectionStringAsync() == defaultConnectionString); + } + + [TestMethod] + public async Task TestGetConnectionString2Async() + { + string defaultConnectionString = "data source=test1;"; + + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + + var options = _services.BuildServiceProvider().GetRequiredService>(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options); + Assert.IsTrue(await provider.GetConnectionStringAsync() == defaultConnectionString); + } + + [TestMethod] + public async Task TestGetConnectionString3Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + Environment = "pro", + ConnectionString = "data source=test3;" + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("pro").Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test3;"); + } + + [TestMethod] + public async Task TestGetConnectionString4Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + Environment = "pro", + ConnectionString = "data source=test3;" + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("Staging").Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test1;"); + } + + [TestMethod] + public async Task TestGetConnectionString5Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + ConnectionString = "data source=test3;" + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("1")).Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, null, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test2;"); + } + + [TestMethod] + public async Task TestGetConnectionString6Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + ConnectionString = "data source=test3;" + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("2")).Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, null, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test3;"); + } + + [TestMethod] + public async Task TestGetConnectionString7Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + ConnectionString = "data source=test3;" + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("11")).Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, null, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test1;"); + } + + [TestMethod] + public async Task TestGetConnectionString8Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + Environment = "dev", + ConnectionString = "data source=test3;" + }, + new() + { + TenantId = "2", + Environment = "pro", + ConnectionString = "data source=test4;" + }, + new() + { + TenantId = "*", + Environment = "pro", + ConnectionString = "data source=test5;", + Score = 99 + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("Staging").Verifiable(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("11")).Verifiable(); + var provider = + new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test1;"); + } + + [TestMethod] + public async Task TestGetConnectionString9Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + Environment = "dev", + ConnectionString = "data source=test3;" + }, + new() + { + TenantId = "2", + Environment = "pro", + ConnectionString = "data source=test4;" + }, + new() + { + TenantId = "*", + Environment = "pro", + ConnectionString = "data source=test5;", + Score = 99 + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("dev").Verifiable(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("1")).Verifiable(); + var provider = + new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test2;"); + } + + [TestMethod] + public async Task TestGetConnectionString10Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + Environment = "dev", + ConnectionString = "data source=test3;" + }, + new() + { + TenantId = "2", + Environment = "pro", + ConnectionString = "data source=test4;" + }, + new() + { + TenantId = "*", + Environment = "pro", + ConnectionString = "data source=test5;", + Score = 99 + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("dev").Verifiable(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("2")).Verifiable(); + var provider = + new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test3;"); + } + + [TestMethod] + public async Task TestGetConnectionString11Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + Environment = "dev", + ConnectionString = "data source=test3;" + }, + new() + { + TenantId = "2", + Environment = "pro", + ConnectionString = "data source=test4;" + }, + new() + { + TenantId = "*", + Environment = "pro", + ConnectionString = "data source=test5;", + Score = 99 + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("pro").Verifiable(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("2")).Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test4;"); + } + + [TestMethod] + public async Task TestGetConnectionString12Async() + { + _services.AddLogging(); + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + Environment = "dev", + ConnectionString = "data source=test3;" + }, + new() + { + TenantId = "2", + Environment = "pro", + ConnectionString = "data source=test4;" + }, + new() + { + TenantId = "*", + Environment = "pro", + ConnectionString = "data source=test5;", + Score = 99 + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("pro").Verifiable(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("2")).Verifiable(); + var provider = new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test4;"); + } + + [TestMethod] + public async Task TestGetConnectionString13Async() + { + string defaultConnectionString = "data source=test1;"; + _services.Configure(option => option.DefaultConnection = defaultConnectionString); + var unitOfWorkAccessor = _services.BuildServiceProvider().GetService()!; + _services.Configure(option => + { + option.DefaultConnection = defaultConnectionString; + option.Isolations = new List + { + new() + { + TenantId = "1", + Environment = "dev", + ConnectionString = "data source=test2;" + }, + new() + { + TenantId = "2", + Environment = "dev", + ConnectionString = "data source=test3;" + }, + new() + { + TenantId = "2", + Environment = "pro", + ConnectionString = "data source=test4;" + }, + new() + { + TenantId = "*", + Environment = "pro", + ConnectionString = "data source=test5;", + Score = 99 + } + }; + }); + var options = _services.BuildServiceProvider().GetRequiredService>(); + + Mock environmentContext = new(); + environmentContext.Setup(context => context.CurrentEnvironment).Returns("pro").Verifiable(); + + Mock tenantContext = new(); + tenantContext.Setup(context => context.CurrentTenant).Returns(new Tenant("10")).Verifiable(); + var provider = + new DefaultDbIsolationConnectionStringProvider(unitOfWorkAccessor, options, environmentContext.Object, tenantContext.Object); + Assert.IsTrue(await provider.GetConnectionStringAsync() == "data source=test5;"); + } + +} diff --git a/test/Masa.Contrib.Isolation.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.Tests/TestIsolation.cs new file mode 100644 index 000000000..1b0198151 --- /dev/null +++ b/test/Masa.Contrib.Isolation.Tests/TestIsolation.cs @@ -0,0 +1,152 @@ +namespace Masa.Contrib.Isolation.Tests; + +[TestClass] +public class TestIsolation +{ + [TestMethod] + public void TestGetDbContextOptionsList() + { + var services = new ServiceCollection(); + services.Configure(option => + { + option.DefaultConnection = "data source=test2"; + option.Isolations = new() + { + new() + { + Environment = "dev", + ConnectionString = "data source=test3" + }, + new() + { + Environment = "pro", + ConnectionString = "data source=test4" + } + }; + }); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var provider = serviceProvider.GetRequiredService(); + Assert.IsTrue(provider.DbContextOptionsList.Distinct().Count() == 3); + } + + [TestMethod] + public void TestUseIsolation() + { + var services = new ServiceCollection(); + Mock eventBuilder = new(); + eventBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + Assert.ThrowsException(() => + { + eventBuilder.Object.UseIsolation(isolationBuilder => + { + }); + }, "Tenant isolation and environment isolation use at least one"); + } + + [TestMethod] + public void TestUseIsolation2() + { + IServiceCollection services = null!; + Mock eventBuilder = new(); + eventBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + Assert.ThrowsException(() => + { + eventBuilder.Object.UseIsolation(isolationBuilder => + { + }); + }); + } + + [TestMethod] + public void TestUseIsolation3() + { + IServiceCollection services = null!; + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + Assert.ThrowsException(() => + { + options.Object.UseIsolation(isolationBuilder => + { + }); + }); + } + + [TestMethod] + public void TestUseIsolation4() + { + IServiceCollection services = new ServiceCollection(); + Mock eventBuilder = new(); + eventBuilder.Setup(builder => builder.Services).Returns(services).Verifiable(); + Assert.ThrowsException(() => + { + eventBuilder.Object.UseIsolation(null!); + }); + } + + [TestMethod] + public void TestUseIsolation5() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + Assert.ThrowsException(() => + { + options.Object.UseIsolation(null!); + }); + } + + [TestMethod] + public void TestUseIsolation6() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + options.Object.UseIsolation(isolationBuilder => isolationBuilder.UseMultiEnvironment()); + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(services.Count(service => service.ServiceType == typeof(IIsolationDbConnectionStringProvider)) == 1); + + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(!serviceProvider.GetServices().Any()); + } + + [TestMethod] + public void TestUseIsolation7() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + + options.Object.UseIsolation(isolationBuilder => isolationBuilder.UseMultiTenant()); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(services.Count(service => service.ServiceType == typeof(IIsolationDbConnectionStringProvider)) == 1); + + Assert.IsTrue(!serviceProvider.GetServices().Any()); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseIsolation8() + { + IServiceCollection services = new ServiceCollection(); + Mock options = new(); + options.Setup(option => option.Services).Returns(services).Verifiable(); + options.Object.UseIsolation(isolationBuilder + => isolationBuilder.UseMultiTenant().UseMultiEnvironment()); + options.Object.UseIsolation(isolationBuilder + => isolationBuilder.UseMultiTenant().UseMultiEnvironment()); + + var serviceProvider = services.BuildServiceProvider(); + Assert.IsTrue(services.Count(service => service.ServiceType == typeof(IIsolationDbConnectionStringProvider)) == 1); + + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseIsolation9() + { + + } +} diff --git a/test/Masa.Contrib.Isolation.Tests/TestParserProvider.cs b/test/Masa.Contrib.Isolation.Tests/TestParserProvider.cs new file mode 100644 index 000000000..ad0d63fc7 --- /dev/null +++ b/test/Masa.Contrib.Isolation.Tests/TestParserProvider.cs @@ -0,0 +1,393 @@ +namespace Masa.Contrib.Isolation.Tests; + +[TestClass] +public class TestParserProvider +{ + [TestMethod] + public async Task TestCookieParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Cookies = new RequestCookieCollection + { + { + tenantKey, "1" + } + } + } + }; + var provider = new CookieParserProvider(); + Assert.IsTrue(provider.Name == "Cookie"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestCookieParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Cookies = new RequestCookieCollection() + } + }; + var provider = new CookieParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => { }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestCookieParser3Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var provider = new CookieParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => { }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestFormParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Form = new FormCollection(new Dictionary + { + { tenantKey, "1" } + } + ) + } + }; + var provider = new FormParserProvider(); + Assert.IsTrue(provider.Name == "Form"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestFormParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Form = new FormCollection(new Dictionary()) + } + }; + var provider = new FormParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestFormParser3Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + QueryString = QueryString.Create(tenantKey, "1") + } + }; + var provider = new FormParserProvider(); + Assert.IsTrue(provider.Name == "Form"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestHeaderParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Headers = + { + { tenantKey, "1" } + } + } + }; + var provider = new HeaderParserProvider(); + Assert.IsTrue(provider.Name == "Header"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestHeaderParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + Headers = { } + } + }; + var provider = new HeaderParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestHttpContextItemParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Items = new Dictionary + { + { tenantKey, "1" } + } + }; + var provider = new HttpContextItemParserProvider(); + Assert.IsTrue(provider.Name == "Items"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestHttpContextItemParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Items = new Dictionary() + }; + var provider = new HttpContextItemParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestHttpContextItemParser3Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var provider = new HttpContextItemParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestQueryStringParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = { QueryString = QueryString.Create(tenantKey, "1") } + }; + var provider = new QueryStringParserProvider(); + Assert.IsTrue(provider.Name == "QueryString"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestQueryStringParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = { QueryString = new QueryString() } + }; + var provider = new QueryStringParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestQueryStringParser3Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var provider = new QueryStringParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestRouteParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + RouteValues = new RouteValueDictionary + { + { tenantKey, "1" } + } + } + }; + var provider = new RouteParserProvider(); + Assert.IsTrue(provider.Name == "Route"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, tenantId => + { + Assert.IsTrue(tenantId == "1"); + }); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestRouteParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext + { + Request = + { + RouteValues = new RouteValueDictionary() + } + }; + var provider = new RouteParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestRouteParser3Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + var provider = new RouteParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider(), tenantKey, _ => + { + }); + Assert.IsFalse(handler); + } + + [TestMethod] + public async Task TestEnvironmentVariablesParserAsync() + { + var services = new ServiceCollection(); + string environmentKey = "env"; + System.Environment.SetEnvironmentVariable(environmentKey, "dev"); + var serviceProvider = services.BuildServiceProvider(); + var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); + var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider, environmentKey, environment => + { + Assert.IsTrue(environment == "dev"); + }); + Assert.IsTrue(environmentVariablesParserProvider.Name == "EnvironmentVariables"); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestEnvironmentVariablesParser2Async() + { + var services = new ServiceCollection(); + Mock environmentSetter = new(); + string environmentKey = "env"; + System.Environment.SetEnvironmentVariable(environmentKey, ""); + environmentSetter.Setup(setter => setter.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + services.Configure(option => + { + option.EnvironmentKey = environmentKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); + var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider, environmentKey, _ => { }); + Assert.IsFalse(handler); + } +} diff --git a/test/Masa.Contrib.Isolation.Tests/UnitOfWorkAccessor.cs b/test/Masa.Contrib.Isolation.Tests/UnitOfWorkAccessor.cs new file mode 100644 index 000000000..1ac176fca --- /dev/null +++ b/test/Masa.Contrib.Isolation.Tests/UnitOfWorkAccessor.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Isolation.Tests; + +public class UnitOfWorkAccessor: IUnitOfWorkAccessor +{ + public MasaDbContextConfigurationOptions? CurrentDbContextOptions { get; set; } +} diff --git a/test/Masa.Contrib.Isolation.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.Tests/_Imports.cs new file mode 100644 index 000000000..a9a00f919 --- /dev/null +++ b/test/Masa.Contrib.Isolation.Tests/_Imports.cs @@ -0,0 +1,19 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Data.UoW.Options; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; +global using Masa.BuildingBlocks.Isolation.MultiTenant; +global using Masa.BuildingBlocks.Isolation.Options; +global using Masa.BuildingBlocks.Isolation.Parser; +global using Masa.Contrib.Isolation.MultiEnvironment; +global using Masa.Contrib.Isolation.MultiTenant; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Routing; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +global using Microsoft.Extensions.Primitives; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System; +global using System.Linq; diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs index 7d7594738..6c8fb6883 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs @@ -4,14 +4,14 @@ public class CustomDbContext : MasaDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { } - public DbSet User { get; set; } + public DbSet User { get; set; } protected override void OnModelCreatingExecuting(ModelBuilder builder) { - builder.Entity(ConfigureUserEntry); + builder.Entity(ConfigureUserEntry); } - void ConfigureUserEntry(EntityTypeBuilder builder) + void ConfigureUserEntry(EntityTypeBuilder builder) { builder.ToTable("Users"); @@ -26,13 +26,13 @@ void ConfigureUserEntry(EntityTypeBuilder builder) } } -public class Users +public class User { public Guid Id { get; private set; } public string Name { get; set; } = default!; - public Users() + public User() { this.Id = Guid.NewGuid(); } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj index 4abf62d6c..585e3abeb 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs index 62f9b5297..4731765b6 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -1,6 +1,3 @@ -using Masa.Contrib.Isolation.MultiTenant; -using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; - namespace Masa.Contrib.Isolation.UoW.EF.Tests; [TestClass] @@ -21,9 +18,9 @@ public void TestUseIsolationUoW() eventBuilder.Setup(builder => builder.Services).Returns(_services).Verifiable(); Assert.ThrowsException(() => { - eventBuilder.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), _ => + eventBuilder.Object.UseIsolationUoW(_ => { - }); + }, dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); }, "Tenant isolation and environment isolation use at least one"); } @@ -34,7 +31,7 @@ public void TestUseIsolationUoW2() eventBuilder.Setup(builder => builder.Services).Returns(_services).Verifiable(); Assert.ThrowsException(() => { - eventBuilder.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), null!); + eventBuilder.Object.UseIsolationUoW(null!, dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); }); } @@ -45,9 +42,9 @@ public void TestUseIsolationUoW3() dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); Assert.ThrowsException(() => { - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), _ => + dispatcherOption.Object.UseIsolationUoW(_ => { - }); + }, dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); }, "Tenant isolation and environment isolation use at least one"); } @@ -58,7 +55,7 @@ public void TestUseIsolationUoW4() dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); Assert.ThrowsException(() => { - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), null!); + dispatcherOption.Object.UseIsolationUoW(null!, dbOptionBuilder => dbOptionBuilder.UseSqlite()); }); } @@ -67,8 +64,7 @@ public void TestUseIsolationUoWByUseEnvironment() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), - isolationBuilder => isolationBuilder.UseEnvironment()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); @@ -80,8 +76,7 @@ public void TestUseIsolationUoWByUseMultiEnvironment() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), - isolationBuilder => isolationBuilder.UseEnvironment().UseEnvironment()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment().UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); @@ -93,8 +88,7 @@ public void TestUseIsolationUoWByUseTenant() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), - isolationBuilder => isolationBuilder.UseMultiTenant()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); @@ -106,8 +100,7 @@ public void TestUseIsolationUoWByUseMultiTenant() { Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), - isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiTenant()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiTenant(), dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString)); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); @@ -124,8 +117,7 @@ public void TestUseIsolation() _services.AddSingleton(configurationRoot); Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), - isolationBuilder => isolationBuilder.UseMultiTenant().UseEnvironment()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -159,16 +151,15 @@ public void TestUseIsolation() var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("development"); var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test1" && - unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test1"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test2" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test2"); var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("development"); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test2" && - unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test2"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test2" && unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test2"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); @@ -180,12 +171,12 @@ public void TestUseIsolation() } [TestMethod] - public void TestUseEnvironment() + public void TestUseMultiEnvironment() { _services.Configure(option => { option.DefaultConnection = "data source=test4"; - option.Isolations = new List() + option.Isolations = new List { new() { @@ -201,8 +192,7 @@ public void TestUseEnvironment() }); Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), - isolationBuilder => isolationBuilder.UseEnvironment()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiEnvironment(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -216,22 +206,19 @@ public void TestUseEnvironment() var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("dev"); var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test5" && - unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test5"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test5" && unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test5"); var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("pro"); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test6" && - unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test6"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test6" && unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test6"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); unifOfWorkNew4.ServiceProvider.GetRequiredService().SetEnvironment("staging"); var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test4" && - unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test4"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test4" && unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test4"); } [TestMethod] @@ -240,7 +227,7 @@ public void TestUseMultiTenant() _services.Configure(option => { option.DefaultConnection = "data source=test7"; - option.Isolations = new List() + option.Isolations = new List { new() { @@ -256,8 +243,7 @@ public void TestUseMultiTenant() }); Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); - dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), - isolationBuilder => isolationBuilder.UseMultiTenant()); + dispatcherOption.Object.UseIsolationUoW(isolationBuilder => isolationBuilder.UseMultiTenant(), dbOptionBuilder => dbOptionBuilder.UseSqlite()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -289,536 +275,5 @@ public void TestUseMultiTenant() unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test7"); } - [TestMethod] - public void TestIsolationBuilder() - { - var services = new ServiceCollection(); - var isolationBuilder = new IsolationBuilder(services); - Assert.IsTrue(isolationBuilder.EnvironmentKey == "ASPNETCORE_ENVIRONMENT"); - Assert.IsTrue(isolationBuilder.TenantKey == "__tenant"); - Assert.IsTrue(isolationBuilder.TenantParsers.Count == 6); - Assert.IsTrue(isolationBuilder.EnvironmentParsers.Count == 1); - - Assert.IsTrue(isolationBuilder.SetTenantKey("tenantId").TenantKey == "tenantId"); - Assert.IsTrue(isolationBuilder.SetEnvironmentKey("dev").EnvironmentKey == "dev"); - Assert.IsTrue(isolationBuilder.SetEnvironmentParsers(new List()).EnvironmentParsers.Count == 0); - Assert.IsTrue(isolationBuilder.SetTenantParsers(new List()).EnvironmentParsers.Count == 0); - } - - [TestMethod] - public async Task TestCookieTenantParserAsync() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - Cookies = new RequestCookieCollection - { - { - tenantKey, "1" - } - } - } - }; - var provider = new CookieTenantParserProvider(); - Assert.IsTrue(provider.Name == "Cookie"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsTrue(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task TestCookieTenantParser2Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - Cookies = new RequestCookieCollection() - } - }; - var provider = new CookieTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestCookieTenantParser3Async() - { - var services = new ServiceCollection(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var provider = new CookieTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestFormTenantParserAsync() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - Form = new FormCollection(new Dictionary() - { - { tenantKey, "1" } - } - ) - } - }; - var provider = new FormTenantParserProvider(); - Assert.IsTrue(provider.Name == "Form"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsTrue(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task TestFormTenantParser2Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - Form = new FormCollection(new Dictionary()) - } - }; - var provider = new FormTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestFormTenantParser3Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - QueryString = QueryString.Create(tenantKey, "1") - } - }; - var provider = new FormTenantParserProvider(); - Assert.IsTrue(provider.Name == "Form"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestHeaderTenantParserAsync() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - Headers = - { - { tenantKey, "1" } - } - } - }; - var provider = new HeaderTenantParserProvider(); - Assert.IsTrue(provider.Name == "Header"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsTrue(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task TestHeaderTenantParser2Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - Headers = { } - } - }; - var provider = new HeaderTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestHttpContextItemTenantParserAsync() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Items = new Dictionary() - { - { tenantKey, "1" } - } - }; - var provider = new HttpContextItemTenantParserProvider(); - Assert.IsTrue(provider.Name == "Items"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsTrue(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task TestHttpContextItemTenantParser2Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Items = new Dictionary() - }; - var provider = new HttpContextItemTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestHttpContextItemTenantParser3Async() - { - var services = new ServiceCollection(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var provider = new HttpContextItemTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestQueryStringTenantParserAsync() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = { QueryString = QueryString.Create(tenantKey, "1") } - }; - var provider = new QueryStringTenantParserProvider(); - Assert.IsTrue(provider.Name == "QueryString"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsTrue(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task TestQueryStringTenantParser2Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = { QueryString = new QueryString() } - }; - var provider = new QueryStringTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestQueryStringTenantParser3Async() - { - var services = new ServiceCollection(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var provider = new QueryStringTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestRouteTenantParserAsync() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - RouteValues = new RouteValueDictionary() - { - { tenantKey, "1" } - } - } - }; - var provider = new RouteTenantParserProvider(); - Assert.IsTrue(provider.Name == "Route"); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsTrue(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); - } - - [TestMethod] - public async Task TestRouteTenantParser2Async() - { - var services = new ServiceCollection(); - services.AddHttpContextAccessor(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext() - { - Request = - { - RouteValues = new RouteValueDictionary() - } - }; - var provider = new RouteTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestRouteTenantParser3Async() - { - var services = new ServiceCollection(); - string tenantKey = "tenant"; - Mock tenantSetter = new(); - tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); - services.AddScoped(_ => tenantSetter.Object); - services.Configure(option => - { - option.TenantKey = tenantKey; - }); - var provider = new RouteTenantParserProvider(); - var handler = await provider.ResolveAsync(services.BuildServiceProvider()); - Assert.IsFalse(handler); - tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); - } - - [TestMethod] - public async Task TestEnvironmentVariablesParserAsync() - { - var services = new ServiceCollection(); - Mock environmentSetter = new(); - string environmentKey = "env"; - environmentSetter.Setup(setter => setter.SetEnvironment(It.IsAny())).Verifiable(); - services.AddScoped(_ => environmentSetter.Object); - services.Configure(option => - { - option.EnvironmentKey = environmentKey; - }); - System.Environment.SetEnvironmentVariable(environmentKey, "dev"); - var serviceProvider = services.BuildServiceProvider(); - var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); - var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider); - Assert.IsTrue(handler); - } - - [TestMethod] - public async Task TestEnvironmentVariablesParser2Async() - { - var services = new ServiceCollection(); - Mock environmentSetter = new(); - string environmentKey = "env"; - System.Environment.SetEnvironmentVariable(environmentKey, ""); - environmentSetter.Setup(setter => setter.SetEnvironment(It.IsAny())).Verifiable(); - services.AddScoped(_ => environmentSetter.Object); - services.Configure(option => - { - option.EnvironmentKey = environmentKey; - }); - var serviceProvider = services.BuildServiceProvider(); - var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); - var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider); - Assert.IsFalse(handler); - } - - [TestMethod] - public void TestGetDbContextOptionsList() - { - var services = new ServiceCollection(); - services.Configure(option => - { - option.DefaultConnection = "data source=test2"; - option.Isolations = new() - { - new() - { - Environment = "dev", - ConnectionString = "data source=test3" - }, - new() - { - Environment = "pro", - ConnectionString = "data source=test4" - } - }; - }); - services.AddSingleton(); - var serviceProvider = services.BuildServiceProvider(); - var provider = serviceProvider.GetRequiredService(); - Assert.IsTrue(provider.DbContextOptionsList.Distinct().Count() == 3); - } - private string GetDataBaseConnectionString(CustomDbContext dbContext) => dbContext.Database.GetConnectionString()!; - } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs index 345f540e1..75e4a3c4f 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs @@ -1,24 +1,19 @@ global using Masa.BuildingBlocks.Data.UoW; global using Masa.BuildingBlocks.Dispatcher.Events; -global using Masa.BuildingBlocks.Isolation; global using Masa.BuildingBlocks.Isolation.Environment; global using Masa.BuildingBlocks.Isolation.MultiTenant; global using Masa.BuildingBlocks.Isolation.Options; -global using Masa.Contrib.Isolation.Environment; -global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; +global using Masa.Contrib.Isolation.MultiEnvironment; +global using Masa.Contrib.Isolation.MultiTenant; global using Masa.Utils.Data.EntityFrameworkCore; global using Masa.Utils.Data.EntityFrameworkCore.Sqlite; -global using Microsoft.AspNetCore.Http; -global using Microsoft.AspNetCore.Routing; global using Microsoft.Data.Sqlite; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Metadata.Builders; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Primitives; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; global using System; global using System.IO; global using System.Linq; - diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs index 75150cd4c..1bc1020d8 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs @@ -4,33 +4,53 @@ public class CustomDbContext : IsolationDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { } - public DbSet User { get; set; } + public DbSet User { get; set; } + + public DbSet Role { get; set; } protected override void OnModelCreatingExecuting(ModelBuilder builder) { - builder.Entity(ConfigureUserEntry); + builder.Entity(ConfigureUserEntry); + builder.Entity(ConfigureRoleEntry); } - void ConfigureUserEntry(EntityTypeBuilder builder) + void ConfigureUserEntry(EntityTypeBuilder builder) { builder.ToTable("Users"); - builder.HasKey(e => e.Id); + builder.HasKey(user => user.Id); - builder.Property(e => e.Id) + builder.Property(user => user.Id) .IsRequired(); - builder.Property(e => e.Account) + builder.Property(user => user.Account) .HasMaxLength(20) .IsRequired(); - builder.Property(e => e.Account) + builder.Property(user => user.Account) .HasMaxLength(50) .IsRequired(); } + + void ConfigureRoleEntry(EntityTypeBuilder builder) + { + builder.ToTable("Roles"); + + builder.HasKey(role => role.Id); + + builder.Property(e => e.Id) + .IsRequired(); + + builder.Property(e => e.Name) + .HasMaxLength(20) + .IsRequired(); + + builder.Property(e => e.Quantity) + .IsRequired(); + } } -public class Users : IIsolation +public class User : IIsolation { public Guid Id { get; private set; } @@ -38,12 +58,32 @@ public class Users : IIsolation public string Password { get; set; } = default!; - public Users() + public int TenantId { get; set; } + + public string Environment { get; set; } + + public User() { this.Id = Guid.NewGuid(); } +} + +public class Role : IIsolation, ISoftDelete +{ + public Guid Id { get; private set; } + + public string Name { get; set; } + + public int Quantity { get; set; } + + public bool IsDeleted { get; set; } public int TenantId { get; set; } public string Environment { get; set; } + + public Role() + { + this.Id = Guid.NewGuid(); + } } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs index 921337ae9..bf7dbba2e 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs @@ -14,8 +14,8 @@ public void Initialize() .Build(); _services = new ServiceCollection(); _services.AddSingleton(configurationRoot); - _services.AddEventBus(eventBusBuilder => eventBusBuilder.UseIsolationUoW(dbOptions => dbOptions.UseSqlite(), - isolationBuilder => isolationBuilder.SetTenantKey("tenant").SetEnvironmentKey("env").UseMultiTenant().UseEnvironment())); + _services.AddEventBus(eventBusBuilder => eventBusBuilder.UseIsolationUoW( + isolationBuilder => isolationBuilder.UseMultiTenant("tenant").UseMultiEnvironment("env"), dbOptions => dbOptions.UseSqlite())); System.Environment.SetEnvironmentVariable("env", "pro"); } @@ -28,7 +28,7 @@ public async Task TestTenantAsync() var httpContextAccessor = serviceProvider.GetRequiredService(); httpContextAccessor.HttpContext = new DefaultHttpContext(); - httpContextAccessor.HttpContext.Items = new Dictionary() + httpContextAccessor.HttpContext.Items = new Dictionary { { "tenant", "2" } }; @@ -39,4 +39,25 @@ public async Task TestTenantAsync() var eventBus = serviceProvider.GetRequiredService(); await eventBus.PublishAsync(registerUserEvent); } + + [TestMethod] + public async Task TestTenant2Async() + { + var serviceProvider = _services.BuildServiceProvider(); + + #region Manually assign values to tenants and environments, and in real scenarios, automatically parse and assign values based on the current HttpContext + + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext(); + httpContextAccessor.HttpContext.Items = new Dictionary + { + { "tenant", "1" } + }; + + #endregion + + var addRoleEvent = new AddRoleEvent("Admin", 1); + var eventBus = serviceProvider.GetRequiredService(); + await eventBus.PublishAsync(addRoleEvent); + } } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/AddRoleEventHandler.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/AddRoleEventHandler.cs new file mode 100644 index 000000000..4ab887962 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/AddRoleEventHandler.cs @@ -0,0 +1,50 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests.EventHandlers; + +public class AddRoleEventHandler +{ + private readonly CustomDbContext _customDbContext; + private readonly IDataFilter _dataFilter; + private readonly IEnvironmentSetter _environmentSetter; + private readonly IEnvironmentContext _environmentContext; + + public AddRoleEventHandler(CustomDbContext customDbContext, IDataFilter dataFilter, IEnvironmentSetter environmentSetter, + IEnvironmentContext environmentContext) + { + _customDbContext = customDbContext; + _dataFilter = dataFilter; + _environmentSetter = environmentSetter; + _environmentContext = environmentContext; + } + + [EventHandler] + public async Task AddRoleAsync(AddRoleEvent @event) + { + await _customDbContext.Database.EnsureCreatedAsync(); + Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test1"); + var role = new Role + { + Name = @event.Name, + Quantity = @event.Quantity + }; + await _customDbContext.Set().AddAsync(role); + await _customDbContext.SaveChangesAsync(); + + var role2 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsTrue(role2!.Name == @event.Name); + Assert.IsTrue(role2.IsDeleted == false); + + _environmentSetter.SetEnvironment("dev"); //In EventHandler, physical isolation is not retriggered if a new DbContext is not recreated, it can only be used to filter changes + Assert.IsTrue(_environmentContext.CurrentEnvironment == "dev"); + Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test1"); + + var role3 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsNull(role3); + + using (_dataFilter.Disable()) + { + Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test1"); + var role4 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsNotNull(role4); + } + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs index f788d9939..0b0a77f02 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs @@ -1,6 +1,4 @@ -using Masa.BuildingBlocks.Isolation.Environment; - -namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests.EventHandlers; +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests.EventHandlers; public class RegisterUserEventHandler { @@ -23,28 +21,30 @@ public async Task RegisterUserAsync(RegisterUserEvent @event) { await _customDbContext.Database.EnsureCreatedAsync(); Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test3"); - var user = new Users() + var user = new User { Account = @event.Account, Password = MD5Utils.Encrypt(@event.Password, @event.Password) }; - await _customDbContext.Set().AddAsync(user); + await _customDbContext.Set().AddAsync(user); await _customDbContext.SaveChangesAsync(); - var user2 = await _customDbContext.Set().FirstOrDefaultAsync(); + var user2 = await _customDbContext.Set().FirstOrDefaultAsync(); Assert.IsTrue(user2!.Account == @event.Account); Assert.IsTrue(user2.Environment == "pro"); Assert.IsTrue(user2.TenantId == 2); _environmentSetter.SetEnvironment("dev"); //In EventHandler, physical isolation is not retriggered if a new DbContext is not recreated, it can only be used to filter changes Assert.IsTrue(_environmentContext.CurrentEnvironment == "dev"); + Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test3"); - var user3 = await _customDbContext.Set().FirstOrDefaultAsync(); + var user3 = await _customDbContext.Set().FirstOrDefaultAsync(); Assert.IsNull(user3); using (_dataFilter.Disable()) { - var user4 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test3"); + var user4 = await _customDbContext.Set().FirstOrDefaultAsync(); Assert.IsNotNull(user4); } } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/AddRoleEvent.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/AddRoleEvent.cs new file mode 100644 index 000000000..458e5ace7 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/AddRoleEvent.cs @@ -0,0 +1,5 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests.Events; + +public record AddRoleEvent(string Name,int Quantity) : Event +{ +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj index 8293b0dce..866c2aa93 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs index df58f49a4..4dae2cacb 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs @@ -1,7 +1,8 @@ global using Masa.BuildingBlocks.Dispatcher.Events; global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; global using Masa.Contrib.Dispatcher.Events; -global using Masa.Contrib.Isolation.Environment; +global using Masa.Contrib.Isolation.MultiEnvironment; global using Masa.Contrib.Isolation.MultiTenant; global using Masa.Contrib.Isolation.UoW.EF.Web.Tests.Events; global using Masa.Utils.Data.EntityFrameworkCore; @@ -16,5 +17,7 @@ global using Microsoft.Extensions.DependencyInjection; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using System; +global using System.Collections.Generic; global using System.IO; global using System.Threading.Tasks; +