diff --git a/.gitignore b/.gitignore index ec39d478..2311d389 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,6 @@ _ReSharper*/ # JetBrains Rider .idea/ -*.sln.iml \ No newline at end of file +*.sln.iml + +.tmp \ No newline at end of file diff --git a/.nuke b/.nuke index 3287f53f..271e41f2 100644 --- a/.nuke +++ b/.nuke @@ -1 +1 @@ -Backend.Fx.sln \ No newline at end of file +src/Backend.Fx.sln diff --git a/.tmp/build-attempt.log b/.tmp/build-attempt.log index b8d03033..c70b1d97 100644 --- a/.tmp/build-attempt.log +++ b/.tmp/build-attempt.log @@ -1,4 +1,5 @@ 884bc421701d4c4e7e62c0eca741ed63 +Clean Restore Compile Test diff --git a/Backend.Fx.sln b/Backend.Fx.sln deleted file mode 100644 index 15e0c8af..00000000 --- a/Backend.Fx.sln +++ /dev/null @@ -1,175 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6B64354E-D95B-4711-BAF6-B32049C90CD9}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{53D4501E-953C-4A7C-97C4-1F9DE04BD092}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C7885592-A4B8-4BA8-8D3A-1EDA4025D17A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.Tests", "tests\Backend.Fx.Tests\Backend.Fx.Tests.csproj", "{3706F748-43F6-41BD-8875-81FA679220C7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCorePersistence.Tests", "tests\Backend.Fx.EfCorePersistence.Tests\Backend.Fx.EfCorePersistence.Tests.csproj", "{4BB72B85-61F2-4C7F-9079-EA43492FCD44}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "abstractions", "abstractions", "{A742F814-725A-44ED-95E6-98E142738E9D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "environments", "environments", "{56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "implementations", "implementations", "{739A7296-579F-4D9A-BC73-DCECD260D7A0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx", "src\abstractions\Backend.Fx\Backend.Fx.csproj", "{581DCC00-9246-4A2E-AE31-206742B2746A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCorePersistence", "src\implementations\Backend.Fx.EfCorePersistence\Backend.Fx.EfCorePersistence.csproj", "{A60B7952-D92C-403D-9710-65BE13963C7E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.NLogLogging", "src\implementations\Backend.Fx.NLogLogging\Backend.Fx.NLogLogging.csproj", "{6F13D33A-37FC-4287-9436-1F6E67CBDD06}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.RabbitMq", "src\implementations\Backend.Fx.RabbitMq\Backend.Fx.RabbitMq.csproj", "{2C826FC0-443A-4874-B213-C35BFDEA200A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.SimpleInjectorDependencyInjection", "src\implementations\Backend.Fx.SimpleInjetorDependencyInjection\Backend.Fx.SimpleInjectorDependencyInjection.csproj", "{FF042FB5-BA44-4655-8903-2644FE549810}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.AspNetCore", "src\environments\Backend.Fx.AspNetCore\Backend.Fx.AspNetCore.csproj", "{25746028-5116-4600-A0C4-35DE0C468A8F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.SimpleInjectorDependencyInjection.Tests", "tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj", "{D98AED23-ABB8-4130-9612-54AEFE9D2272}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.NetCore", "src\environments\Backend.Fx.NetCore\Backend.Fx.NetCore.csproj", "{45EC5987-1C85-4940-8E5E-3B4F0FA90AF8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.InMemoryPersistence", "src\implementations\Backend.Fx.InMemoryPersistence\Backend.Fx.InMemoryPersistence.csproj", "{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.Log4NetLogging", "src\implementations\Backend.Fx.Log4NetLogging\Backend.Fx.Log4NetLogging.csproj", "{C27BA4CE-882B-405F-906E-4DFA6E9F1216}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.RabbitMq.Tests", "tests\Backend.Fx.RabbitMq.Tests\Backend.Fx.RabbitMq.Tests.csproj", "{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.SerilogLogging", "src\implementations\Backend.Fx.SerilogLogging\Backend.Fx.SerilogLogging.csproj", "{33F7D896-2276-4DD7-A4DA-8FD5C47F7735}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.AspNetCore.Tests", "tests\Backend.Fx.AspNetCore.Tests\Backend.Fx.AspNetCore.Tests.csproj", "{DF40E1E8-FB19-455E-9CED-212C544AA8BC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{16EBBF6D-EA66-4E14-BE2D-1900CBC747F7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{FD21EC43-FC48-433A-8C4F-5CCFC1A2B35E}" -ProjectSection(SolutionItems) = preProject - .github\workflows\ci.yaml = .github\workflows\ci.yaml -EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\_build.csproj", "{B708C5F2-7557-43A1-AEE4-4EF798B06F7C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistence", "persistence", "{ADC35CAD-F5B1-42B6-A0CC-B96974C11F11}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logging", "logging", "{71A095A4-E8FC-4895-84AD-F2E91AFE0629}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "messagebus", "messagebus", "{8BC1C02F-0785-4161-BC37-7D462BD6F42D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencyinjection", "dependencyinjection", "{22E4DE95-C3E5-49E6-83BF-BF30905A746B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DFD5E4B8-2479-4D29-9857-9199B94E412A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFD5E4B8-2479-4D29-9857-9199B94E412A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFD5E4B8-2479-4D29-9857-9199B94E412A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3706F748-43F6-41BD-8875-81FA679220C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3706F748-43F6-41BD-8875-81FA679220C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3706F748-43F6-41BD-8875-81FA679220C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3706F748-43F6-41BD-8875-81FA679220C7}.Release|Any CPU.Build.0 = Release|Any CPU - {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BB72B85-61F2-4C7F-9079-EA43492FCD44}.Release|Any CPU.Build.0 = Release|Any CPU - {581DCC00-9246-4A2E-AE31-206742B2746A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {581DCC00-9246-4A2E-AE31-206742B2746A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {581DCC00-9246-4A2E-AE31-206742B2746A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {581DCC00-9246-4A2E-AE31-206742B2746A}.Release|Any CPU.Build.0 = Release|Any CPU - {A60B7952-D92C-403D-9710-65BE13963C7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A60B7952-D92C-403D-9710-65BE13963C7E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A60B7952-D92C-403D-9710-65BE13963C7E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A60B7952-D92C-403D-9710-65BE13963C7E}.Release|Any CPU.Build.0 = Release|Any CPU - {6F13D33A-37FC-4287-9436-1F6E67CBDD06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F13D33A-37FC-4287-9436-1F6E67CBDD06}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F13D33A-37FC-4287-9436-1F6E67CBDD06}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F13D33A-37FC-4287-9436-1F6E67CBDD06}.Release|Any CPU.Build.0 = Release|Any CPU - {2C826FC0-443A-4874-B213-C35BFDEA200A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C826FC0-443A-4874-B213-C35BFDEA200A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C826FC0-443A-4874-B213-C35BFDEA200A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C826FC0-443A-4874-B213-C35BFDEA200A}.Release|Any CPU.Build.0 = Release|Any CPU - {FF042FB5-BA44-4655-8903-2644FE549810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF042FB5-BA44-4655-8903-2644FE549810}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF042FB5-BA44-4655-8903-2644FE549810}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF042FB5-BA44-4655-8903-2644FE549810}.Release|Any CPU.Build.0 = Release|Any CPU - {25746028-5116-4600-A0C4-35DE0C468A8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25746028-5116-4600-A0C4-35DE0C468A8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25746028-5116-4600-A0C4-35DE0C468A8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25746028-5116-4600-A0C4-35DE0C468A8F}.Release|Any CPU.Build.0 = Release|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Release|Any CPU.Build.0 = Release|Any CPU - {45EC5987-1C85-4940-8E5E-3B4F0FA90AF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45EC5987-1C85-4940-8E5E-3B4F0FA90AF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45EC5987-1C85-4940-8E5E-3B4F0FA90AF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45EC5987-1C85-4940-8E5E-3B4F0FA90AF8}.Release|Any CPU.Build.0 = Release|Any CPU - {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.Build.0 = Release|Any CPU - {C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C27BA4CE-882B-405F-906E-4DFA6E9F1216}.Release|Any CPU.Build.0 = Release|Any CPU - {6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}.Release|Any CPU.Build.0 = Release|Any CPU - {33F7D896-2276-4DD7-A4DA-8FD5C47F7735}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33F7D896-2276-4DD7-A4DA-8FD5C47F7735}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33F7D896-2276-4DD7-A4DA-8FD5C47F7735}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33F7D896-2276-4DD7-A4DA-8FD5C47F7735}.Release|Any CPU.Build.0 = Release|Any CPU - {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF40E1E8-FB19-455E-9CED-212C544AA8BC}.Release|Any CPU.Build.0 = Release|Any CPU - {B708C5F2-7557-43A1-AEE4-4EF798B06F7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B708C5F2-7557-43A1-AEE4-4EF798B06F7C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B708C5F2-7557-43A1-AEE4-4EF798B06F7C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B708C5F2-7557-43A1-AEE4-4EF798B06F7C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {3706F748-43F6-41BD-8875-81FA679220C7} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} - {4BB72B85-61F2-4C7F-9079-EA43492FCD44} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} - {A742F814-725A-44ED-95E6-98E142738E9D} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092} - {56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092} - {739A7296-579F-4D9A-BC73-DCECD260D7A0} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092} - {581DCC00-9246-4A2E-AE31-206742B2746A} = {A742F814-725A-44ED-95E6-98E142738E9D} - {25746028-5116-4600-A0C4-35DE0C468A8F} = {56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A} - {D98AED23-ABB8-4130-9612-54AEFE9D2272} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} - {45EC5987-1C85-4940-8E5E-3B4F0FA90AF8} = {56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A} - {6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} - {DF40E1E8-FB19-455E-9CED-212C544AA8BC} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} - {16EBBF6D-EA66-4E14-BE2D-1900CBC747F7} = {6B64354E-D95B-4711-BAF6-B32049C90CD9} - {FD21EC43-FC48-433A-8C4F-5CCFC1A2B35E} = {16EBBF6D-EA66-4E14-BE2D-1900CBC747F7} - {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11} = {739A7296-579F-4D9A-BC73-DCECD260D7A0} - {A60B7952-D92C-403D-9710-65BE13963C7E} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11} - {0B8F13CA-1347-4655-9D41-AED21B1AFAC4} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11} - {71A095A4-E8FC-4895-84AD-F2E91AFE0629} = {739A7296-579F-4D9A-BC73-DCECD260D7A0} - {C27BA4CE-882B-405F-906E-4DFA6E9F1216} = {71A095A4-E8FC-4895-84AD-F2E91AFE0629} - {6F13D33A-37FC-4287-9436-1F6E67CBDD06} = {71A095A4-E8FC-4895-84AD-F2E91AFE0629} - {33F7D896-2276-4DD7-A4DA-8FD5C47F7735} = {71A095A4-E8FC-4895-84AD-F2E91AFE0629} - {8BC1C02F-0785-4161-BC37-7D462BD6F42D} = {739A7296-579F-4D9A-BC73-DCECD260D7A0} - {2C826FC0-443A-4874-B213-C35BFDEA200A} = {8BC1C02F-0785-4161-BC37-7D462BD6F42D} - {22E4DE95-C3E5-49E6-83BF-BF30905A746B} = {739A7296-579F-4D9A-BC73-DCECD260D7A0} - {FF042FB5-BA44-4655-8903-2644FE549810} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969} - EndGlobalSection -EndGlobal diff --git a/Backend.Fx.sln.DotSettings b/Backend.Fx.sln.DotSettings deleted file mode 100644 index afc9e37f..00000000 --- a/Backend.Fx.sln.DotSettings +++ /dev/null @@ -1,7 +0,0 @@ - - True - True - True - True - True - True \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index 8777529b..f359749e 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -17,8 +17,8 @@ class Build : NukeBuild { public static int Main() => Execute(x => x.Publish); - [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] - readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; + [Parameter("Configuration to build - Default is 'Release'")] + readonly Configuration Configuration = Configuration.Release; readonly string MygetApiKey = Environment.GetEnvironmentVariable("MYGET_APIKEY"); readonly string MygetFeedUrl = Environment.GetEnvironmentVariable("MYGET_FEED_URL") ?? "https://www.myget.org/F/marcwittke/api/v3/index.json"; @@ -26,10 +26,9 @@ class Build : NukeBuild [Solution] readonly Solution Solution; [GitRepository] readonly GitRepository GitRepository; - [GitVersion(Framework = "netcoreapp3.1")] readonly GitVersion GitVersion; + [GitVersion(Framework = "net5.0", NoFetch = true)] readonly GitVersion GitVersion; AbsolutePath SourceDirectory => RootDirectory / "src"; - AbsolutePath TestsDirectory => RootDirectory / "tests"; AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; Target Clean => _ => _ @@ -37,7 +36,6 @@ class Build : NukeBuild .Executes(() => { SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); - TestsDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); EnsureCleanDirectory(ArtifactsDirectory); }); diff --git a/build/Configuration.cs b/build/Configuration.cs index 9c08b1ae..9b22a3bd 100644 --- a/build/Configuration.cs +++ b/build/Configuration.cs @@ -1,6 +1,4 @@ -using System; using System.ComponentModel; -using System.Linq; using Nuke.Common.Tooling; [TypeConverter(typeof(TypeConverter))] diff --git a/build/_build.csproj b/build/_build.csproj index fb5af998..088ecbfd 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 CS0649;CS0169 .. @@ -11,7 +11,7 @@ - + diff --git a/doc/Backend.Fx.eap b/doc/Backend.Fx.eap deleted file mode 100644 index 87a898ec..00000000 Binary files a/doc/Backend.Fx.eap and /dev/null differ diff --git a/lib/GitVersion/GitTools.Core.dll b/lib/GitVersion/GitTools.Core.dll deleted file mode 100644 index 550f39c2..00000000 Binary files a/lib/GitVersion/GitTools.Core.dll and /dev/null differ diff --git a/lib/GitVersion/GitVersion.deps.json b/lib/GitVersion/GitVersion.deps.json deleted file mode 100644 index bd4751ad..00000000 --- a/lib/GitVersion/GitVersion.deps.json +++ /dev/null @@ -1,1569 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v2.0", - "signature": "644b446cf5bc23cbbe0b5c6e71dd04dd866a5bd4" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v2.0": { - "GitVersion/4.0.0": { - "dependencies": { - "GitVersionCore": "1.0.0" - }, - "runtime": { - "GitVersion.dll": {} - } - }, - "GitTools.Core/1.3.1": { - "dependencies": { - "JetBrains.Annotations": "10.4.0", - "LibGit2Sharp": "0.25.0-preview-0033", - "Microsoft.CSharp": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Runtime.Serialization.Formatters": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/GitTools.Core.dll": { - "assemblyVersion": "1.0.0.0", - "fileVersion": "1.0.0.0" - } - } - }, - "JetBrains.Annotations/10.4.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/JetBrains.Annotations.dll": { - "assemblyVersion": "10.4.0.0", - "fileVersion": "10.4.0.0" - } - } - }, - "LibGit2Sharp/0.25.0-preview-0033": { - "dependencies": { - "LibGit2Sharp.NativeBinaries": "1.0.185", - "System.Diagnostics.TraceSource": "4.0.0", - "System.IO.UnmanagedMemoryStream": "4.0.1", - "System.Security.SecureString": "4.0.0" - }, - "runtime": { - "lib/netstandard1.3/LibGit2Sharp.dll": { - "assemblyVersion": "0.25.0.0", - "fileVersion": "0.25.0.33" - } - } - }, - "LibGit2Sharp.NativeBinaries/1.0.185": { - "runtimeTargets": { - "runtimes/linux-x64/native/libgit2-15e1193.so": { - "rid": "linux-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/osx/native/libgit2-15e1193.dylib": { - "rid": "osx", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win7-x64/native/git2-15e1193.dll": { - "rid": "win7-x64", - "assetType": "native", - "fileVersion": "0.26.0.0" - }, - "runtimes/win7-x64/native/git2-15e1193.pdb": { - "rid": "win7-x64", - "assetType": "native", - "fileVersion": "0.0.0.0" - }, - "runtimes/win7-x86/native/git2-15e1193.dll": { - "rid": "win7-x86", - "assetType": "native", - "fileVersion": "0.26.0.0" - }, - "runtimes/win7-x86/native/git2-15e1193.pdb": { - "rid": "win7-x86", - "assetType": "native", - "fileVersion": "0.0.0.0" - } - } - }, - "Microsoft.CSharp/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Globalization": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "Microsoft.NETCore.Targets/1.1.0": {}, - "Newtonsoft.Json/10.0.3": { - "dependencies": { - "Microsoft.CSharp": "4.3.0", - "System.ComponentModel.TypeConverter": "4.3.0", - "System.Runtime.Serialization.Formatters": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/Newtonsoft.Json.dll": { - "assemblyVersion": "10.0.0.0", - "fileVersion": "10.0.3.21018" - } - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/debian.8-x64/native/_._": { - "rid": "debian.8-x64", - "assetType": "native" - } - } - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/fedora.23-x64/native/_._": { - "rid": "fedora.23-x64", - "assetType": "native" - } - } - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/fedora.24-x64/native/_._": { - "rid": "fedora.24-x64", - "assetType": "native" - } - } - }, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/opensuse.13.2-x64/native/_._": { - "rid": "opensuse.13.2-x64", - "assetType": "native" - } - } - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/opensuse.42.1-x64/native/_._": { - "rid": "opensuse.42.1-x64", - "assetType": "native" - } - } - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "runtimeTargets": { - "runtime/osx.10.10-x64/native/_._": { - "rid": "osx.10.10-x64", - "assetType": "native" - } - } - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/osx.10.10-x64/native/_._": { - "rid": "osx.10.10-x64", - "assetType": "native" - } - } - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/rhel.7-x64/native/_._": { - "rid": "rhel.7-x64", - "assetType": "native" - } - } - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/ubuntu.14.04-x64/native/_._": { - "rid": "ubuntu.14.04-x64", - "assetType": "native" - } - } - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/ubuntu.16.04-x64/native/_._": { - "rid": "ubuntu.16.04-x64", - "assetType": "native" - } - } - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "runtimeTargets": { - "runtime/ubuntu.16.10-x64/native/_._": { - "rid": "ubuntu.16.10-x64", - "assetType": "native" - } - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Collections.NonGeneric/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Collections.Specialized/4.3.0": { - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.ComponentModel/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.ComponentModel.Primitives/4.3.0": { - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.ComponentModel.TypeConverter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.Collections.Specialized": "4.3.0", - "System.ComponentModel": "4.3.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Globalization": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Diagnostics.TraceSource/4.0.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.IO.UnmanagedMemoryStream/4.0.1": { - "dependencies": { - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Linq.Expressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Net.Http/4.3.2": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.2", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.ObjectModel/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Runtime.Serialization.Formatters/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0" - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtimeTargets": { - "runtime/osx/lib/_._": { - "rid": "osx", - "assetType": "runtime" - }, - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - } - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Security.SecureString/4.0.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtimeTargets": { - "runtime/unix/lib/_._": { - "rid": "unix", - "assetType": "runtime" - }, - "runtime/win/lib/_._": { - "rid": "win", - "assetType": "runtime" - } - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Xml.ReaderWriter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - } - }, - "System.Xml.XmlDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "YamlDotNet/4.2.3": { - "runtime": { - "lib/netstandard1.3/YamlDotNet.dll": { - "assemblyVersion": "4.2.3.0", - "fileVersion": "4.2.3.0" - } - } - }, - "GitVersionCore/1.0.0": { - "dependencies": { - "GitTools.Core": "1.3.1", - "LibGit2Sharp.NativeBinaries": "1.0.185", - "Newtonsoft.Json": "10.0.3", - "System.Net.Http": "4.3.2", - "System.Net.Requests": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "YamlDotNet": "4.2.3" - }, - "runtime": { - "GitVersionCore.dll": {} - } - } - } - }, - "libraries": { - "GitVersion/4.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "GitTools.Core/1.3.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-o6j310+SCwbPx1epYE24M9kKXpB/XZGBOjwVqoK9aGRJBrEznzxGIzZx7T2AtqCxn2mu9HCMsEUjNktheg2SbQ==", - "path": "gittools.core/1.3.1", - "hashPath": "gittools.core.1.3.1.nupkg.sha512" - }, - "JetBrains.Annotations/10.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BHOL9R1ueuzAJTHvWpczsxCFoKBzv2ya3zC7mvj4Rp1HkFNSMZOPTNSYSwwTmva/Lv3DHmqOTZ50IBOQh8Lg1g==", - "path": "jetbrains.annotations/10.4.0", - "hashPath": "jetbrains.annotations.10.4.0.nupkg.sha512" - }, - "LibGit2Sharp/0.25.0-preview-0033": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZDh4oj9OQU+7bIgpbZy2+vbAIRBt5f8bGfBSYKRtcZoSSRiBXxVUyHoy+HTpCMylWRRCQVwxCsO4acYNUrE90A==", - "path": "libgit2sharp/0.25.0-preview-0033", - "hashPath": "libgit2sharp.0.25.0-preview-0033.nupkg.sha512" - }, - "LibGit2Sharp.NativeBinaries/1.0.185": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oTfbwzSAS3KuTlf4ZqjuKSKv8B0dAmf3WaXtgn+5lg1+CNkYKUefibY6KPpzlg62ymDMU10AJtoIvF7x7nZLhw==", - "path": "libgit2sharp.nativebinaries/1.0.185", - "hashPath": "libgit2sharp.nativebinaries.1.0.185.nupkg.sha512" - }, - "Microsoft.CSharp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uRTtEI6VsC47SGYSJArwBsSgZOE2AliZOqMBWYqy3MQelyQ8T7mpWCW4xbJNGgrfXCHDUEeq4WXmzxBgZ78ybA==", - "path": "microsoft.csharp/4.3.0", - "hashPath": "microsoft.csharp.4.3.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytLLWSzd1X4/wJDCh3AVJCpyMjlt1gM5oHvIb7MvVGzNgSflpyccYmuisFGU5Uc79JahYmVYwgGhc5ZBypTBDA==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "Newtonsoft.Json/10.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==", - "path": "newtonsoft.json/10.0.3", - "hashPath": "newtonsoft.json.10.0.3.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/JzPg7iKsFwIS7FPKZIRJlNcTD7rBQ3kryyoSEPPfxH5gMaPNmH+kjVT2PrBay95Qm+ZiurF9GcDDidPGEQJFA==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qK3bdi9Sm8lFFj36PmzhzODRQM/gzZ8Ba5v1SeHW054JWi7EGJNjau34iifZwNC90MogBg1SoT47YI8L6Y+aMA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DbmGaB2n28O3sAKIyA+WoJUcFbQm/gilvMVBZg9S6jXAlnc+mfY5E19lNpJeC44mK7af7OSlp/akF9EiJMl2GQ==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-G8iY4meBzEGlulw6VGXCaq8Nzi5ZBSRHAY9w9X6jT0J5gczMTfk3gYy94Txhm/mvZa5pb6Z23m/AmMW+wv8Ysw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-T1QjeXl/NQXWs/2/QBIUVpgmltA3HgwoKuRXND8ObYl4s2OrKrvRJt19v343rO1evJOlAHd/tW2m69FkByqR/A==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-pzYnyEvvsRoVijEFB1AQH6fHaFo5FDhWRolAL6Rot/d7kM5jO49ZOnF1sgcbU7NM7b+mQ1sVFQB3+Yly7g4xCw==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+7v8iH2VTcC4q407O0kCYzIpHDS10htbQNS5MFRRwJfnRpsu85zKSmpxr0JN07JTc6tkkjf86f1Iy4k3EkX9IA==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-8X1iUaCCwDgVStQddnxdUm+tuljV6NwRMIhS8AFHj3sAciMBWaeyqvjGDSM4kXAkTBXAt8+BaanizaEBmLmtjw==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TcGZ/v1sOBMshBnDA1Sx5oNZj9nml0fM7+r44dqHoICbu2vNR3phzP3zNnNg2EnTCh+jR2dVkQtLpMXw+ODBiA==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-08fmISekELabJEOfDvPhYiWit5mJBTGAlAgFrz+mZVPNp9RnLO9yHKdXmwfUH11gp4Vm6erBXppFV9Fw5wu0TQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-iWzRg0DAMJMGH/czofpsYVfuj12ZCHVA3bnzteTCljBeIl2+r8xWJCpvW1TEP1VkG8CnTxuYArSsMvxB++5hxw==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nccseIr+I5DFeqq91p4G8/ScuMSnJcxUaOLcVcBPmC/+eQr8P8pZMQTTitZp9J8sX0Lmb/Sq83ZnjaTVlaFISA==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-WDem6DAUgzOVSBXzWo8pp6XxTO7B9q969GVcEVvt4GCcWXYNmSEBWJMe9WiAcfzu5aDcWywXCxrmld3QDbJBEg==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-betAfW/S1V3BwoiRmQRYEmmDTCjhdUPNOfvtP+hIdxuk6ZxM1N0GtbywCzSooxctM6E6G33vXe2rEXSBndelQQ==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-200eFB0N9eZBbJpX4B2LGl6vyXZ1XbtKgO7LFxAT9/EXb5q3l7VIdzIX70sGwyucSVq2xYAqX7cGlZwr8Pu+jg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-IBm+Q1J1tm1WL8i75kHNGMciYO++PKuUCG5t+mjcZ7bseyz553hk+eMzDCj7PA4KBxH231a/9WYb8lhrDv1iRw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5ulfVe0tzqwrRQMEIh6vObV+kmppVjD55Dd0UE6mZTy/5msJB0X9L/9STB09xYzHZiOi7bf85QGCDdFPVSs40A==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.Collections.NonGeneric/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7Th8cODnHIgyE26aUcj0J4byBHtP99iWPUtKlIQpyFRBeU4tHw6tJnHNvjLi+WA/6TZp5M44TZoRY8U/iEJlyQ==", - "path": "system.collections.nongeneric/4.3.0", - "hashPath": "system.collections.nongeneric.4.3.0.nupkg.sha512" - }, - "System.Collections.Specialized/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZeAwMt4Up4TulTM5h2DQZ2hHuxYj3upLAbhbLRHY+77R6Pj6z4Z6n6IE1Kn7oi76Fgb3M4QVJQUYvsW97tHJMQ==", - "path": "system.collections.specialized/4.3.0", - "hashPath": "system.collections.specialized.4.3.0.nupkg.sha512" - }, - "System.ComponentModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vI932IM2wdu+hSFFKdgxPsm6CH7C+xvHWCGYIHQu2IJrPrqvsDpTctjAN50modDsc71m2qoHrLQgmU9Us9euSg==", - "path": "system.componentmodel/4.3.0", - "hashPath": "system.componentmodel.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Sb8LdAjOUMYSDNqQu4L2xoyAWTQGRlfZhDEEmoC4QH2t3H6m1DY4dM5mP4CDDux8LbcILjcYOSdWHP0ZA4+QTQ==", - "path": "system.componentmodel.primitives/4.3.0", - "hashPath": "system.componentmodel.primitives.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.TypeConverter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3eQr72QADP4Eh2EWPHrNIzztviAJzG7A1ejEB8FywnK0XVwbWXOg1N5bXIw7RM4gW0w9qifDyn/Xwm0NjkGmJQ==", - "path": "system.componentmodel.typeconverter/4.3.0", - "hashPath": "system.componentmodel.typeconverter.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-n32WASxghwyuvm8kdm5sfQGWBGUV0YqGqcWIFHZlQ346GWNJkWNiZcZgXnpVfqhltZuGHq6oXoO45Dc90d/ezw==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-21u64agQmuHbdzBcAcxJurl/QRweHIwTgBzi+lXiHs1p51EujOeVnsENgdXp4m5QS1esOUwCZXpyzMU2o49biw==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.TraceSource/4.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6WVCczFZKXwpWpzd/iJkYnsmWTSFFiU24Xx/YdHXBcu+nFI/ehTgeqdJQFbtRPzbrO3KtRNjvkhtj4t5/WwWsA==", - "path": "system.diagnostics.tracesource/4.0.0", - "hashPath": "system.diagnostics.tracesource.4.0.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-pqo6To9izvwPgRiTZnxvslKx784R/DBEqtWV1AXkizx77SQw+oufpr/puMMYgBKJFR2vcexlQjjUnRCEEDMRjQ==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Dynamic.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YXIggGcZ0xaiFCvZ0bepRuIFqwmx7/T3B/VmFAP212WDurdm1SluabnMIUQ0KmbTDXV5LteMntqekxpmWQ9nOA==", - "path": "system.dynamic.runtime/4.3.0", - "hashPath": "system.dynamic.runtime.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5ay4bV2/N1RP5XY8xUjQ/qjcdwxkb4vlpuoafj74xrohY6sh6jkNWCWmfycZE4/5qlOxZdtF3wL+KLYdp+7irw==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-V7aviGxAYYUHbBjVGDJS0ljyI/qc6OqAPZJSm+p+2QXEgz+0NzOQY9RRsRrnF5GOcqBdsb83xtKI7Z4tZILfdQ==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-x1QhWE0qjXjy+QK5320lOsO91xgpqAHh0h90N7NmDquyKw+gA2MC1fDPfT6j8s0XmODqZPT63jLEGaxEf+uAtA==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-L5YORr8xPIUjmF7mvHMtxTePiHKxsTUckAACjt7fzhHYJPih4WaJwptI9ZyInkdRaOCVjVCoIogsLSR2uWkwsw==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7koUKdgxrorsAtibjawf0SjfE550fh+MzsjbsDJnh1nOLGFyhwRiAP6nSGV1uQ7WTH+Zpj7bJSsSS8ekM+3/3g==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RsyQPypek/lKwW6fEn/IRnKVXLjfvgm5zHQGqlZOZzskMOiFs2fkwLSxc/Z7FDbjr5X5qgA2VooBWz7gkDWCxA==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.IO.UnmanagedMemoryStream/4.0.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FelqNIIUYdzvSIGnC+qU5vFWv59IKnjlbG0euoQp90KktruY9fr/7uskWoOVj3L9vGHmz6D7UoPhfR/NIdJf/A==", - "path": "system.io.unmanagedmemorystream/4.0.1", - "hashPath": "system.io.unmanagedmemorystream.4.0.1.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LQhiK6Bsl3fMskpyJ32yh2H9N+m98F4JHiNB1wLQsb9Ct0WJg1ExstYA1jnNknj78fXZwjjnTfL4n+GbjY9LkA==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Linq.Expressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nYIClsbYRMHfhD9KOHYrGn9P//jljwc71noboKNYfnS5bLklNr2MhpGMnSlDW7iETD5demPQCLYMXoGDY3Kjtw==", - "path": "system.linq.expressions/4.3.0", - "hashPath": "system.linq.expressions.4.3.0.nupkg.sha512" - }, - "System.Net.Http/4.3.2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-mnICWRW9zmjV1ZEoL9b4/sS2vAOL8hCPEh/XOPfvjDNw3PSUnaJpG9lPEEkzNka7OZFJ5/VwhNoQtz9Egtr8HA==", - "path": "system.net.http/4.3.2", - "hashPath": "system.net.http.4.3.2.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YfOFWRUd20IOJZ1W9rqr3YHNNI0jbKCYwAMk1IGpypwDSpuQwDJloE67T+4AH6ZqjUslI/SA67lvzlVYPadv5g==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rsqJjCGL1BhkvMxUnO0XMsJTDxWUjh08yg8TSn72U294l+9iT0aYuc6z0+oOpjv8IMqf1/cYRsxRywBBULnuiw==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bIDhN5r4fB+CEu7lUYQIqsPbgGP6bHYVCptoNuEEVQRPkOrVRpXpehqLz+e7XzGsc1hToSJ+MR2sfMOQ+/rk7A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.ObjectModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Nskf2SYcbNQvAHWUgnZABh7dPKqHtSkeFaqzHGUAqHXmeDZmE2SwrxcCmlIBtsvk7yeSZEd975J7zcQ4Uewb/g==", - "path": "system.objectmodel/4.3.0", - "hashPath": "system.objectmodel.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qT7GlIYUEz3NmWBtF06oUbjQMrbtDcw4hCjhKDz3wjHbHMuVvkBKZztn64sJ1AwgtmWLmD7Bn7QHTLooiaXSPw==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rbk1mShyEO0tWEBacr2yVM/ur5NCaii6IhYEaslZwF7f7JO2BZ+lVX6Mo8klzy7fT2T5eishZrv3F4Lvw5AzWg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6G4nJb+/mmQSngUQGK/4xlVWYKgAyQPiMP9QAEG/ZKCY41FNcFixPC719nEe4pCvU4fTigTyUQpR1KSIbReYHw==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uoqB3X+WPxzMh1UoPwDlWgTGulmh1OkhGQQeQVMH6l/fq6scQnOfN1WGctzXGWVSOyLlgn1mUyfkPbBfPIvXBg==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YbRxNhNYsk4f6G+5/9Ne+v6sWczhWjARfaEGPzZcmdVuOKGt05DI1Z6TfGKZTLVqocGQjh+iQuiKem7jtSpu0g==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ix3iL33E9DdpLwJa087WQTvan+QuEfwHQZqf+2hjb58Gn4Vi/qVaOCo7tNnb5+l8szXKywSSM0//ucUIyF870g==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7j4nPW7N4I3i08NuKE6ZYhENaF0nXrrukVQpSG+Cbn5iLHU4jrDAHHu/3Cgjj+pZgYORy7RCnXDOeAaIg2pqmg==", - "path": "system.reflection.typeextensions/4.3.0", - "hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-zCYivSL6Sp67gMkcczNln8WYw7Y1dGa8CPVTs385VbB25g11Kk5xoO8TytK6Qb5HO8n0AHCNsp6Ltv7EEazh1Q==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KijrInhiP+YGs4ZKcT1sGdgrftjfH4gZrBRKJfKsTvvqFclaA6hGeWzXLU2XJ2nNy3P7htJ4g9UDE+KjLANTCQ==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FbMqvUhS2mHvUrBQdje6QnAf7SPmnx48CN9iuBJ18+E5TKA+Tn5eFxvkVIUZjkIkeepYm4Ap5Rq5BTnVG5jHnA==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Kd0mxsNyAsHtPUZE+qt+4NFlMiygxzo3r3vrfxeJJpJhze5gWJaECBx4xSVkJJftHsMCroH0unOsrKlV1/IQhg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Z0k1/sYZTjiuiAB+5xL1sobx4cfOqJK18hh00lROU7yN3iBHueQDuAhYCMzgj3a9J8d/tj4SJV1VdteNGpg/wA==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-28DIjO6Spmo7UBXb3jN78LzsykrOh2+Zq0fKlr5sMFP7Q9BS/wqO+EbD2aRFMc9wzsZO6rBszA42MqKiD2UyNA==", - "path": "system.runtime.interopservices.runtimeinformation/4.3.0", - "hashPath": "system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-pIzda6H/tQVF+2tBGxpVdoHeOFM5bFEQaT+7mEgntJqCdvAv+pB7F4dDkOtBHR/Ci0uc+XKdV89XI5zVdoa9iw==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Formatters/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==", - "path": "system.runtime.serialization.formatters/4.3.0", - "hashPath": "system.runtime.serialization.formatters.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-mbwZ7frfdkCI+5IEze5kZu1WoCbEAM+3djovVzL2uKfaY4+7aTSV0lHHcHxuZvtg+qLxpcFXd2nWhankPSHD0g==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-2g6di4TACljLFEzS+qINo5TC0kk1BHBcAyTGwL3ifyRV8Dn+ZnN60L72hlOucgiD5nDLFcZir0hxITAh72NVpQ==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lP///2kB2Uvvab6l55PCw3vmr7QKMsg2pUiDloaXP/k/3FC/zDoEdBaHcJlNNYOgg/bMnYnrfaCZaVgI4yzFLg==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-x527jRyFOXaEXNpt2WyYeJ58/KaDAKDZm+6ZBbvRREyyfXHimPeikG8KpgVE2Df5S79OXHIAq14sKx2L8Sw+xg==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-CbOyxqpm4kvHwXwsySefYGTJwmUtwYnR000G0fuBQhxgVF0bx5KJKfH8uXrJWm9P1JSp9RzpKF/paEO4YZ0vDA==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1QfJjHG0543QWiWhjbAXeDKvbAL1xO+Hfy7hEkY3ZpayJqIGwx40cG9Ht99ZXQnBEx7nY7u7cd/SoB83p2HTXg==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6/gmeY2MRzLqmxS7GI4dw19ALo8UGq43tNXljIehWW9wvYwtuC/QoA05FkCOEP+Cfg5YcJc079+p+MDMRffyTA==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/G5ZjHxJPImt4zn8VQGSmdX8svLdt5oBX4AivpK9DNTBhyhz0Alp6IzJmI222qFzz1PnOGbdW9KJRL9Cld4xGQ==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.SecureString/4.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7TGOnj9Lr8ljCJbMHjZC34hEw3Z+zRPp7eNhLBg22mbSqO8gQMGLJ/vQkWv8HFYG0t2i53ZulKZ8NNho+jVK7Q==", - "path": "system.security.securestring/4.0.0", - "hashPath": "system.security.securestring.4.0.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-SzIbqxzENo10YtPeMhvqI0dfCqE4Q+Fud7YF7jEP4MuZ3Nza9w+QGOFQJ+hyg7WIDtRKsN0cnkodSW5//6kqVw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Ne/tEJYVXxMYOLdpMZ4KFYTyT4GrWS7zM+enXSRFsCWHVnTizKgwaXgQ80JdrPJLtjoGdBvTOsnTyU0rSGf1wQ==", - "path": "system.text.encoding.extensions/4.3.0", - "hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bP8xKEESq6EOKFvRYGUwPkLGtZre1Y3QQKLmY21WgNLTxDs8Aff0AeKEXZL0TgkL8SrIkE7lCnXFTi9MqdAHMg==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7VnBJCVkxFIES+qCjPN/sGN0RWI5xAE7/kfD2qfJPBMEoMeFzI74bb7CjL0C97TJ1uN35Ah85mM4acCPh0zyBA==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9Mdk6qutu+3TRSWxzJaC9Sdm3BNYX34FJ2g2Oct/be/BT46JMGexURivTZbkQxL48W4RxvTtG0CZHMRnmbi+Dg==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+nyHzRMqBfXjeWbACNqwps8+n2JJJc7E0ALT0dGCaYobvvznjEwiNl6FXhpm/eAIz6FRL3GPRi3B37+R3yHnKw==", - "path": "system.threading.tasks.extensions/4.3.0", - "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" - }, - "System.Xml.ReaderWriter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ffATtFd2XaSXv3bdR0sYLUMgmUdoY3LvjE4DOJmdHyw6gn9gdZSCPZXdoI74eJ08cz1r7rLf8U7oROqB5xkQ4Q==", - "path": "system.xml.readerwriter/4.3.0", - "hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "path": "system.xml.xmldocument/4.3.0", - "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" - }, - "YamlDotNet/4.2.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZAGjxozwteyECBckC3m6qJrIXGoYCttlinvsi7c3GgM1rgM/YeR+pNy8Blz7SHk2/vjzzpJdw3seso/MUqpVSA==", - "path": "yamldotnet/4.2.3", - "hashPath": "yamldotnet.4.2.3.nupkg.sha512" - }, - "GitVersionCore/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/GitVersion/GitVersion.dll b/lib/GitVersion/GitVersion.dll deleted file mode 100644 index ecd8e092..00000000 Binary files a/lib/GitVersion/GitVersion.dll and /dev/null differ diff --git a/lib/GitVersion/GitVersion.dll.config b/lib/GitVersion/GitVersion.dll.config deleted file mode 100644 index 87ccfc55..00000000 --- a/lib/GitVersion/GitVersion.dll.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/lib/GitVersion/GitVersion.pdb b/lib/GitVersion/GitVersion.pdb deleted file mode 100644 index 109ca323..00000000 Binary files a/lib/GitVersion/GitVersion.pdb and /dev/null differ diff --git a/lib/GitVersion/GitVersion.runtimeconfig.json b/lib/GitVersion/GitVersion.runtimeconfig.json deleted file mode 100644 index 7539019b..00000000 --- a/lib/GitVersion/GitVersion.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp2.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "2.0.0" - } - } -} \ No newline at end of file diff --git a/lib/GitVersion/GitVersion.xml b/lib/GitVersion/GitVersion.xml deleted file mode 100644 index a4ea331a..00000000 --- a/lib/GitVersion/GitVersion.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - GitVersion - - - - diff --git a/lib/GitVersion/GitVersionCore.dll b/lib/GitVersion/GitVersionCore.dll deleted file mode 100644 index 2a0a5347..00000000 Binary files a/lib/GitVersion/GitVersionCore.dll and /dev/null differ diff --git a/lib/GitVersion/GitVersionCore.dll.config b/lib/GitVersion/GitVersionCore.dll.config deleted file mode 100644 index 5fafb00c..00000000 --- a/lib/GitVersion/GitVersionCore.dll.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/lib/GitVersion/GitVersionCore.pdb b/lib/GitVersion/GitVersionCore.pdb deleted file mode 100644 index 4e42255a..00000000 Binary files a/lib/GitVersion/GitVersionCore.pdb and /dev/null differ diff --git a/lib/GitVersion/JetBrains.Annotations.dll b/lib/GitVersion/JetBrains.Annotations.dll deleted file mode 100644 index 5e00e3be..00000000 Binary files a/lib/GitVersion/JetBrains.Annotations.dll and /dev/null differ diff --git a/lib/GitVersion/LICENSE b/lib/GitVersion/LICENSE deleted file mode 100644 index 7e5992d6..00000000 --- a/lib/GitVersion/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 NServiceBus Ltd - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/GitVersion/LibGit2Sharp.dll b/lib/GitVersion/LibGit2Sharp.dll deleted file mode 100644 index 1e18e2ae..00000000 Binary files a/lib/GitVersion/LibGit2Sharp.dll and /dev/null differ diff --git a/lib/GitVersion/LibGit2Sharp.dll.config b/lib/GitVersion/LibGit2Sharp.dll.config deleted file mode 100644 index 09e21cd4..00000000 --- a/lib/GitVersion/LibGit2Sharp.dll.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lib/GitVersion/Newtonsoft.Json.dll b/lib/GitVersion/Newtonsoft.Json.dll deleted file mode 100644 index 96725e64..00000000 Binary files a/lib/GitVersion/Newtonsoft.Json.dll and /dev/null differ diff --git a/lib/GitVersion/YamlDotNet.dll b/lib/GitVersion/YamlDotNet.dll deleted file mode 100644 index c4bf46d3..00000000 Binary files a/lib/GitVersion/YamlDotNet.dll and /dev/null differ diff --git a/lib/GitVersion/gitversion.sh b/lib/GitVersion/gitversion.sh deleted file mode 100644 index 22a2d3fc..00000000 --- a/lib/GitVersion/gitversion.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")") -dotnet ${scriptDir}/GitVersion.dll /updateassemblyinfo /output buildserver - -echo "$GitVersion_Major" -echo "$GitVersion_Minor" -echo "$GitVersion_Patch" -echo "$GitVersion_PreReleaseTag" -echo "$GitVersion_PreReleaseTagWithDash" -echo "$GitVersion_PreReleaseLabel" -echo "$GitVersion_PreReleaseNumber" -echo "$GitVersion_BuildMetaData" -echo "$GitVersion_BuildMetaDataPadded" -echo "$GitVersion_FullBuildMetaData" -echo "$GitVersion_MajorMinorPatch" -echo "$GitVersion_SemVer" -echo "$GitVersion_LegacySemVer" -echo "$GitVersion_LegacySemVerPadded" -echo "$GitVersion_AssemblySemVer" -echo "$GitVersion_FullSemVer" -echo "$GitVersion_InformationalVersion" -echo "$GitVersion_BranchName" -echo "$GitVersion_Sha" -echo "$GitVersion_NuGetVersionV2" -echo "$GitVersion_NuGetVersion" -echo "$GitVersion_CommitsSinceVersionSource" -echo "$GitVersion_CommitsSinceVersionSourcePadded" -echo "$GitVersion_CommitDate" \ No newline at end of file diff --git a/lib/GitVersion/lib/linux/x86_64/libgit2-15e1193.so b/lib/GitVersion/lib/linux/x86_64/libgit2-15e1193.so deleted file mode 100644 index 77fdccc7..00000000 Binary files a/lib/GitVersion/lib/linux/x86_64/libgit2-15e1193.so and /dev/null differ diff --git a/lib/GitVersion/lib/osx/libgit2-15e1193.dylib b/lib/GitVersion/lib/osx/libgit2-15e1193.dylib deleted file mode 100644 index fdbe85b5..00000000 Binary files a/lib/GitVersion/lib/osx/libgit2-15e1193.dylib and /dev/null differ diff --git a/lib/GitVersion/lib/win32/x64/git2-15e1193.dll b/lib/GitVersion/lib/win32/x64/git2-15e1193.dll deleted file mode 100644 index b1ea1e8c..00000000 Binary files a/lib/GitVersion/lib/win32/x64/git2-15e1193.dll and /dev/null differ diff --git a/lib/GitVersion/lib/win32/x64/git2-15e1193.pdb b/lib/GitVersion/lib/win32/x64/git2-15e1193.pdb deleted file mode 100644 index 027bfa65..00000000 Binary files a/lib/GitVersion/lib/win32/x64/git2-15e1193.pdb and /dev/null differ diff --git a/lib/GitVersion/lib/win32/x86/git2-15e1193.dll b/lib/GitVersion/lib/win32/x86/git2-15e1193.dll deleted file mode 100644 index 3503a6d9..00000000 Binary files a/lib/GitVersion/lib/win32/x86/git2-15e1193.dll and /dev/null differ diff --git a/lib/GitVersion/lib/win32/x86/git2-15e1193.pdb b/lib/GitVersion/lib/win32/x86/git2-15e1193.pdb deleted file mode 100644 index 5a6d5aae..00000000 Binary files a/lib/GitVersion/lib/win32/x86/git2-15e1193.pdb and /dev/null differ diff --git a/lib/GitVersion/runtimes/linux-x64/native/libgit2-15e1193.so b/lib/GitVersion/runtimes/linux-x64/native/libgit2-15e1193.so deleted file mode 100644 index 77fdccc7..00000000 Binary files a/lib/GitVersion/runtimes/linux-x64/native/libgit2-15e1193.so and /dev/null differ diff --git a/lib/GitVersion/runtimes/osx/native/libgit2-15e1193.dylib b/lib/GitVersion/runtimes/osx/native/libgit2-15e1193.dylib deleted file mode 100644 index fdbe85b5..00000000 Binary files a/lib/GitVersion/runtimes/osx/native/libgit2-15e1193.dylib and /dev/null differ diff --git a/lib/GitVersion/runtimes/win7-x64/native/git2-15e1193.dll b/lib/GitVersion/runtimes/win7-x64/native/git2-15e1193.dll deleted file mode 100644 index b1ea1e8c..00000000 Binary files a/lib/GitVersion/runtimes/win7-x64/native/git2-15e1193.dll and /dev/null differ diff --git a/lib/GitVersion/runtimes/win7-x64/native/git2-15e1193.pdb b/lib/GitVersion/runtimes/win7-x64/native/git2-15e1193.pdb deleted file mode 100644 index 027bfa65..00000000 Binary files a/lib/GitVersion/runtimes/win7-x64/native/git2-15e1193.pdb and /dev/null differ diff --git a/lib/GitVersion/runtimes/win7-x86/native/git2-15e1193.dll b/lib/GitVersion/runtimes/win7-x86/native/git2-15e1193.dll deleted file mode 100644 index 3503a6d9..00000000 Binary files a/lib/GitVersion/runtimes/win7-x86/native/git2-15e1193.dll and /dev/null differ diff --git a/lib/GitVersion/runtimes/win7-x86/native/git2-15e1193.pdb b/lib/GitVersion/runtimes/win7-x86/native/git2-15e1193.pdb deleted file mode 100644 index 5a6d5aae..00000000 Binary files a/lib/GitVersion/runtimes/win7-x86/native/git2-15e1193.pdb and /dev/null differ diff --git a/src/Backend.Fx.sln b/src/Backend.Fx.sln new file mode 100644 index 00000000..2ad1c4de --- /dev/null +++ b/src/Backend.Fx.sln @@ -0,0 +1,128 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "abstractions", "abstractions", "{B33F43E8-5160-4650-8DD2-025FFEFB6F62}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "environments", "environments", "{D9C426CB-6378-45E9-9092-B5EB9428CB44}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "implementations", "implementations", "{B0AFAE5E-CDD8-4F50-9571-D8C44D33DF39}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx", "abstractions\Backend.Fx\Backend.Fx.csproj", "{1482FD1F-DD95-43C0-8A61-778286A93957}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.Tests", "abstractions\Backend.Fx.Tests\Backend.Fx.Tests.csproj", "{7EFFE412-3204-488F-830B-398ACB7A3C6F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencyinjection", "dependencyinjection", "{8CD77C83-25D6-4BAA-8E87-5D5A4D7AD7E3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.SimpleInjectorDependencyInjection", "implementations\dependencyinjection\Backend.Fx.SimpleInjetorDependencyInjection\Backend.Fx.SimpleInjectorDependencyInjection.csproj", "{55C6D938-CFF8-406B-AEFC-D20060BB9EE9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.SimpleInjectorDependencyInjection.Tests", "implementations\dependencyinjection\Backend.Fx.SimpleInjectorDependencyInjection.Tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj", "{CE35157D-B13C-440D-89CC-EE6CBD5DEAFF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logging", "logging", "{970F2241-2981-4E69-BDA3-31C7119718D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.Log4NetLogging", "implementations\logging\Backend.Fx.Log4NetLogging\Backend.Fx.Log4NetLogging.csproj", "{34A4CA92-D417-47B4-A6FD-B63E9B040DB8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.NLogLogging", "implementations\logging\Backend.Fx.NLogLogging\Backend.Fx.NLogLogging.csproj", "{150C5CB7-4C8B-416F-8C06-21773BE30351}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.SerilogLogging", "implementations\logging\Backend.Fx.SerilogLogging\Backend.Fx.SerilogLogging.csproj", "{86158D4A-9C71-4A27-8E2C-CCE165836E17}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistence", "persistence", "{C3724AAF-FC05-4EA7-88E0-59ABAEE50834}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCorePersistence", "implementations\persistence\Backend.Fx.EfCorePersistence\Backend.Fx.EfCorePersistence.csproj", "{27E1D903-8351-4B36-A79C-9EAFC5FCD45F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCorePersistence.Tests", "implementations\persistence\Backend.Fx.EfCorePersistence.Tests\Backend.Fx.EfCorePersistence.Tests.csproj", "{053F1EF1-28FC-457B-AF6D-AFE60E2B4DC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.InMemoryPersistence", "implementations\persistence\Backend.Fx.InMemoryPersistence\Backend.Fx.InMemoryPersistence.csproj", "{26060E36-A818-465D-8AA0-0ECFFD8766E2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integrationeventaggregation", "integrationeventaggregation", "{AE474726-EE44-47E0-A218-D25836D929C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.RabbitMq", "implementations\integrationeventaggregation\Backend.Fx.RabbitMq\Backend.Fx.RabbitMq.csproj", "{69B65627-1461-4AF7-B46A-D4C3AB3E8D17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.RabbitMq.Tests", "implementations\integrationeventaggregation\Backend.Fx.RabbitMq.Tests\Backend.Fx.RabbitMq.Tests.csproj", "{155BFE1B-98EA-4974-A6C9-91BDE555858B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.AspNetCore", "environments\Backend.Fx.AspNetCore\Backend.Fx.AspNetCore.csproj", "{DAA34009-35AE-4B87-862D-FF37FABBA3A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.AspNetCore.Tests", "environments\Backend.Fx.AspNetCore.Tests\Backend.Fx.AspNetCore.Tests.csproj", "{E9236584-EEED-44EE-A65F-5406CBF50E25}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1482FD1F-DD95-43C0-8A61-778286A93957} = {B33F43E8-5160-4650-8DD2-025FFEFB6F62} + {7EFFE412-3204-488F-830B-398ACB7A3C6F} = {B33F43E8-5160-4650-8DD2-025FFEFB6F62} + {8CD77C83-25D6-4BAA-8E87-5D5A4D7AD7E3} = {B0AFAE5E-CDD8-4F50-9571-D8C44D33DF39} + {55C6D938-CFF8-406B-AEFC-D20060BB9EE9} = {8CD77C83-25D6-4BAA-8E87-5D5A4D7AD7E3} + {CE35157D-B13C-440D-89CC-EE6CBD5DEAFF} = {8CD77C83-25D6-4BAA-8E87-5D5A4D7AD7E3} + {970F2241-2981-4E69-BDA3-31C7119718D9} = {B0AFAE5E-CDD8-4F50-9571-D8C44D33DF39} + {34A4CA92-D417-47B4-A6FD-B63E9B040DB8} = {970F2241-2981-4E69-BDA3-31C7119718D9} + {150C5CB7-4C8B-416F-8C06-21773BE30351} = {970F2241-2981-4E69-BDA3-31C7119718D9} + {86158D4A-9C71-4A27-8E2C-CCE165836E17} = {970F2241-2981-4E69-BDA3-31C7119718D9} + {C3724AAF-FC05-4EA7-88E0-59ABAEE50834} = {B0AFAE5E-CDD8-4F50-9571-D8C44D33DF39} + {27E1D903-8351-4B36-A79C-9EAFC5FCD45F} = {C3724AAF-FC05-4EA7-88E0-59ABAEE50834} + {053F1EF1-28FC-457B-AF6D-AFE60E2B4DC5} = {C3724AAF-FC05-4EA7-88E0-59ABAEE50834} + {26060E36-A818-465D-8AA0-0ECFFD8766E2} = {C3724AAF-FC05-4EA7-88E0-59ABAEE50834} + {AE474726-EE44-47E0-A218-D25836D929C0} = {B0AFAE5E-CDD8-4F50-9571-D8C44D33DF39} + {69B65627-1461-4AF7-B46A-D4C3AB3E8D17} = {AE474726-EE44-47E0-A218-D25836D929C0} + {155BFE1B-98EA-4974-A6C9-91BDE555858B} = {AE474726-EE44-47E0-A218-D25836D929C0} + {DAA34009-35AE-4B87-862D-FF37FABBA3A8} = {D9C426CB-6378-45E9-9092-B5EB9428CB44} + {E9236584-EEED-44EE-A65F-5406CBF50E25} = {D9C426CB-6378-45E9-9092-B5EB9428CB44} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1482FD1F-DD95-43C0-8A61-778286A93957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1482FD1F-DD95-43C0-8A61-778286A93957}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1482FD1F-DD95-43C0-8A61-778286A93957}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1482FD1F-DD95-43C0-8A61-778286A93957}.Release|Any CPU.Build.0 = Release|Any CPU + {7EFFE412-3204-488F-830B-398ACB7A3C6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EFFE412-3204-488F-830B-398ACB7A3C6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EFFE412-3204-488F-830B-398ACB7A3C6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EFFE412-3204-488F-830B-398ACB7A3C6F}.Release|Any CPU.Build.0 = Release|Any CPU + {55C6D938-CFF8-406B-AEFC-D20060BB9EE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55C6D938-CFF8-406B-AEFC-D20060BB9EE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55C6D938-CFF8-406B-AEFC-D20060BB9EE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55C6D938-CFF8-406B-AEFC-D20060BB9EE9}.Release|Any CPU.Build.0 = Release|Any CPU + {CE35157D-B13C-440D-89CC-EE6CBD5DEAFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE35157D-B13C-440D-89CC-EE6CBD5DEAFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE35157D-B13C-440D-89CC-EE6CBD5DEAFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE35157D-B13C-440D-89CC-EE6CBD5DEAFF}.Release|Any CPU.Build.0 = Release|Any CPU + {34A4CA92-D417-47B4-A6FD-B63E9B040DB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34A4CA92-D417-47B4-A6FD-B63E9B040DB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34A4CA92-D417-47B4-A6FD-B63E9B040DB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34A4CA92-D417-47B4-A6FD-B63E9B040DB8}.Release|Any CPU.Build.0 = Release|Any CPU + {150C5CB7-4C8B-416F-8C06-21773BE30351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {150C5CB7-4C8B-416F-8C06-21773BE30351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {150C5CB7-4C8B-416F-8C06-21773BE30351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {150C5CB7-4C8B-416F-8C06-21773BE30351}.Release|Any CPU.Build.0 = Release|Any CPU + {86158D4A-9C71-4A27-8E2C-CCE165836E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86158D4A-9C71-4A27-8E2C-CCE165836E17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86158D4A-9C71-4A27-8E2C-CCE165836E17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86158D4A-9C71-4A27-8E2C-CCE165836E17}.Release|Any CPU.Build.0 = Release|Any CPU + {27E1D903-8351-4B36-A79C-9EAFC5FCD45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27E1D903-8351-4B36-A79C-9EAFC5FCD45F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27E1D903-8351-4B36-A79C-9EAFC5FCD45F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27E1D903-8351-4B36-A79C-9EAFC5FCD45F}.Release|Any CPU.Build.0 = Release|Any CPU + {053F1EF1-28FC-457B-AF6D-AFE60E2B4DC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {053F1EF1-28FC-457B-AF6D-AFE60E2B4DC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {053F1EF1-28FC-457B-AF6D-AFE60E2B4DC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {053F1EF1-28FC-457B-AF6D-AFE60E2B4DC5}.Release|Any CPU.Build.0 = Release|Any CPU + {26060E36-A818-465D-8AA0-0ECFFD8766E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26060E36-A818-465D-8AA0-0ECFFD8766E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26060E36-A818-465D-8AA0-0ECFFD8766E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26060E36-A818-465D-8AA0-0ECFFD8766E2}.Release|Any CPU.Build.0 = Release|Any CPU + {69B65627-1461-4AF7-B46A-D4C3AB3E8D17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69B65627-1461-4AF7-B46A-D4C3AB3E8D17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69B65627-1461-4AF7-B46A-D4C3AB3E8D17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69B65627-1461-4AF7-B46A-D4C3AB3E8D17}.Release|Any CPU.Build.0 = Release|Any CPU + {155BFE1B-98EA-4974-A6C9-91BDE555858B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {155BFE1B-98EA-4974-A6C9-91BDE555858B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {155BFE1B-98EA-4974-A6C9-91BDE555858B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {155BFE1B-98EA-4974-A6C9-91BDE555858B}.Release|Any CPU.Build.0 = Release|Any CPU + {DAA34009-35AE-4B87-862D-FF37FABBA3A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAA34009-35AE-4B87-862D-FF37FABBA3A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAA34009-35AE-4B87-862D-FF37FABBA3A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAA34009-35AE-4B87-862D-FF37FABBA3A8}.Release|Any CPU.Build.0 = Release|Any CPU + {E9236584-EEED-44EE-A65F-5406CBF50E25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9236584-EEED-44EE-A65F-5406CBF50E25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9236584-EEED-44EE-A65F-5406CBF50E25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9236584-EEED-44EE-A65F-5406CBF50E25}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Backend.Fx.sln.DotSettings b/src/Backend.Fx.sln.DotSettings new file mode 100644 index 00000000..3e4ad954 --- /dev/null +++ b/src/Backend.Fx.sln.DotSettings @@ -0,0 +1,86 @@ + + DoNotTouch + True + True + False + True + True + False + False + False + False + True + False + False + False + False + True + True + False + True + True + True + True + True + True + True + True + False + False + False + False + False + False + False + False + False + False + False + False + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + 1 + 1 + 1 + 1 + 2 + 1 + 10000 + 120 + HINT + SUGGESTION + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + UseVarWhenEvident + UseVarWhenEvident + Required + Required + Required + Required + TOGETHER + NEVER + NEVER + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + diff --git a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj b/src/abstractions/Backend.Fx.Tests/Backend.Fx.Tests.csproj similarity index 74% rename from tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj rename to src/abstractions/Backend.Fx.Tests/Backend.Fx.Tests.csproj index fe6f8fd0..8cbeb9d2 100644 --- a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj +++ b/src/abstractions/Backend.Fx.Tests/Backend.Fx.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 @@ -19,9 +19,9 @@ - - - + + + diff --git a/tests/Backend.Fx.Tests/Backup.bak b/src/abstractions/Backend.Fx.Tests/Backup.bak similarity index 100% rename from tests/Backend.Fx.Tests/Backup.bak rename to src/abstractions/Backend.Fx.Tests/Backup.bak diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs similarity index 87% rename from tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs rename to src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs index 8a336b70..63dcc823 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs +++ b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs @@ -11,40 +11,12 @@ public class TheAggregateRoot { private static int _nextId; - public class TestAggregateRoot : AggregateRoot - { - public TestAggregateRoot(int id, string name) : base(id) - { - Name = name; - Children.Add(new TestEntity("Child 1", this)); - Children.Add(new TestEntity("Child 2", this)); - Children.Add(new TestEntity("Child 3", this)); - } - - [UsedImplicitly] public string Name { get; private set; } - - public ISet Children { get; } = new HashSet(); - } - - public class TestEntity : Entity - { - public TestEntity(string name, TestAggregateRoot parent) - { - Name = name; - Parent = parent; - } - - [UsedImplicitly] public string Name { get; set; } - - [UsedImplicitly] public TestAggregateRoot Parent { get; set; } - } - [Fact] public void ChangedByPropertyIsChoppedAt100Chars() { - DateTime now = DateTime.Now; + var now = DateTime.Now; var sut = new TestAggregateRoot(_nextId++, "gaga"); - var moreThanHundred = Letters.RandomLowerCase(110); + string moreThanHundred = Letters.RandomLowerCase(110); sut.SetModifiedProperties(moreThanHundred, now); Assert.Equal(moreThanHundred.Substring(0, 99) + "…", sut.ChangedBy); } @@ -52,7 +24,7 @@ public void ChangedByPropertyIsChoppedAt100Chars() [Fact] public void ChangedByPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + var now = DateTime.Now; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetModifiedProperties("me", now); Assert.Equal("me", sut.ChangedBy); @@ -62,7 +34,7 @@ public void ChangedByPropertyIsStoredCorrectly() [Fact] public void ChangedOnPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + var now = DateTime.Now; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetModifiedProperties("me", now); Assert.Equal(now, sut.ChangedOn); @@ -72,9 +44,9 @@ public void ChangedOnPropertyIsStoredCorrectly() [Fact] public void CreatedByPropertyIsChoppedAt100Chars() { - DateTime now = DateTime.Now; + var now = DateTime.Now; var sut = new TestAggregateRoot(_nextId++, "gaga"); - var moreThanHundred = Letters.RandomLowerCase(110); + string moreThanHundred = Letters.RandomLowerCase(110); sut.SetCreatedProperties(moreThanHundred, now); Assert.Equal(moreThanHundred.Substring(0, 99) + "…", sut.CreatedBy); } @@ -82,7 +54,7 @@ public void CreatedByPropertyIsChoppedAt100Chars() [Fact] public void CreatedByPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + var now = DateTime.Now; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetCreatedProperties("me", now); Assert.Equal("me", sut.CreatedBy); @@ -92,7 +64,7 @@ public void CreatedByPropertyIsStoredCorrectly() [Fact] public void CreatedOnPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + var now = DateTime.Now; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetCreatedProperties("me", now); Assert.Equal(now, sut.CreatedOn); @@ -130,5 +102,38 @@ public void ThrowsGivenNullCreatedBy() // ReSharper disable once AssignNullToNotNullAttribute Assert.Throws(() => sut.SetCreatedProperties(null, DateTime.Now)); } + + + public class TestAggregateRoot : AggregateRoot + { + public TestAggregateRoot(int id, string name) : base(id) + { + Name = name; + Children.Add(new TestEntity("Child 1", this)); + Children.Add(new TestEntity("Child 2", this)); + Children.Add(new TestEntity("Child 3", this)); + } + + [UsedImplicitly] + public string Name { get; private set; } + + public ISet Children { get; } = new HashSet(); + } + + + public class TestEntity : Entity + { + public TestEntity(string name, TestAggregateRoot parent) + { + Name = name; + Parent = parent; + } + + [UsedImplicitly] + public string Name { get; set; } + + [UsedImplicitly] + public TestAggregateRoot Parent { get; set; } + } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheIdentified.cs b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheIdentified.cs new file mode 100644 index 00000000..7da7d92b --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheIdentified.cs @@ -0,0 +1,31 @@ +using Backend.Fx.BuildingBlocks; +using Xunit; + +namespace Backend.Fx.Tests.BuildingBlocks +{ + public class TestIdentified : Identified + { + public TestIdentified(int id) + { + Id = id; + } + } + + + public class TheIdentified + { + [Fact] + public void IsEquatable() + { + var identified1 = new TestIdentified(1); + var identified1Clone = new TestIdentified(1); + var identified2 = new TestIdentified(2); + Identified stillNull = null; + + Assert.True(identified1.Equals(identified1)); + Assert.True(identified1.Equals(identified1Clone)); + Assert.False(identified1.Equals(identified2)); + Assert.False(identified1.Equals(stillNull)); + } + } +} diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs similarity index 68% rename from tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs rename to src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs index 800ab514..3a953b83 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs +++ b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs @@ -16,11 +16,14 @@ public void AcceptsNullArrayToResolve() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); Assert.Empty(sut.Resolve(null)); } @@ -29,22 +32,25 @@ public void CanResolveListOfIds() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 234}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); sut.Store.Add(agg3.Id, agg3); sut.Store.Add(agg4.Id, agg4); - var resolved = sut.Resolve(new[] {23, 24, 25, 26}); + TheAggregateRoot.TestAggregateRoot[] resolved = sut.Resolve(new[] { 23, 24, 25, 26 }); Assert.Equal(4, resolved.Length); Assert.Contains(agg1, resolved); Assert.Contains(agg2, resolved); @@ -55,35 +61,40 @@ public void CanResolveListOfIds() [Fact] public void ThrowsOnAttemptToAddNull() { - var sut = new InMemoryRepository(new InMemoryStore(), - CurrentTenantIdHolder.Create(234), - new AllowAll()); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + new AllowAll()); Assert.Throws(() => sut.AddRange(null!)); Assert.Throws(() => sut.Add(null!)); } - + [Fact] public void ThrowsOnAttemptToDeleteNull() { - var sut = new InMemoryRepository(new InMemoryStore(), - CurrentTenantIdHolder.Create(234), - new AllowAll()); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + new AllowAll()); Assert.Throws(() => sut.Delete(null!)); } - + [Fact] public void DeletesItemFromMyTenant() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); A.CallTo(() => authorization.CanDelete(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") {TenantId = 234}; + var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); sut.Delete(agg1); @@ -96,18 +107,25 @@ public void DoesNotReturnItemsFromOtherTenants() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); var store = new InMemoryStore(); - var sut = new InMemoryRepository(store, CurrentTenantIdHolder.Create(234), authorization); + var sut = new InMemoryRepository( + store, + CurrentTenantIdHolder.Create(234), + authorization); sut.Add(new TheAggregateRoot.TestAggregateRoot(22, "1")); sut.Add(new TheAggregateRoot.TestAggregateRoot(23, "2")); sut.Add(new TheAggregateRoot.TestAggregateRoot(24, "3")); // now I am in another tenant - sut = new InMemoryRepository(store, CurrentTenantIdHolder.Create(233), authorization); + sut = new InMemoryRepository( + store, + CurrentTenantIdHolder.Create(233), + authorization); Assert.Empty(sut.AggregateQueryable); } @@ -116,11 +134,14 @@ public void MaintainsTenantIdOnAdd() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); var agg1 = new TheAggregateRoot.TestAggregateRoot(22, "1"); sut.Add(agg1); @@ -132,17 +153,20 @@ public void ProvidesCorrectAny() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); Assert.False(sut.Any()); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 234}; + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); @@ -157,17 +181,20 @@ public void ReturnsAll() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(12123124, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(12123125, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(12123126, "whatever") {TenantId = 234}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(12123124, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(12123125, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(12123126, "whatever") { TenantId = 234 }; - sut.AddRange(new[] {agg1, agg2, agg3, agg4}); + sut.AddRange(new[] { agg1, agg2, agg3, agg4 }); Assert.Equal(4, sut.GetAll().Length); Assert.Contains(agg1, sut.GetAll()); @@ -181,15 +208,18 @@ public void ReturnsByIdOnSingle() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 234}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); @@ -208,15 +238,18 @@ public void ReturnsByIdOnSingleOrDefault() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 234}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); @@ -234,11 +267,14 @@ public void ReturnsByIdOnSingleOrDefault() public void ReturnsEmptyWhenTenantIdHolderIsEmpty() { var authorization = A.Fake>(); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(null), + authorization); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); Assert.Empty(sut.AggregateQueryable); } @@ -249,22 +285,25 @@ public void ReturnsOnlyAuthorizedRecords() var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) - .ReturnsLazily((IQueryable q) => q.Where(agg => agg.Id == 25 || agg.Id == 26)); + .ReturnsLazily( + (IQueryable q) => q.Where(agg => agg.Id == 25 || agg.Id == 26)); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 234}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); sut.Store.Add(agg3.Id, agg3); sut.Store.Add(agg4.Id, agg4); - var all = sut.GetAll(); + TheAggregateRoot.TestAggregateRoot[] all = sut.GetAll(); Assert.Equal(2, all.Length); Assert.DoesNotContain(agg1, all); Assert.DoesNotContain(agg2, all); @@ -277,7 +316,8 @@ public void ReturnsOnlyItemsFromMyTenant() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); var agg1 = new TheAggregateRoot.TestAggregateRoot(11, "1"); @@ -287,22 +327,34 @@ public void ReturnsOnlyItemsFromMyTenant() var agg5 = new TheAggregateRoot.TestAggregateRoot(15, "5"); var store = new InMemoryStore(); - var sut = new InMemoryRepository(store, CurrentTenantIdHolder.Create(234), authorization); + var sut = new InMemoryRepository( + store, + CurrentTenantIdHolder.Create(234), + authorization); sut.Add(agg1); sut.Add(agg2); sut.Add(agg3); - sut = new InMemoryRepository(store, CurrentTenantIdHolder.Create(567), authorization); + sut = new InMemoryRepository( + store, + CurrentTenantIdHolder.Create(567), + authorization); sut.Add(agg4); sut.Add(agg5); - sut = new InMemoryRepository(store, CurrentTenantIdHolder.Create(234), authorization); + sut = new InMemoryRepository( + store, + CurrentTenantIdHolder.Create(234), + authorization); Assert.Equal(3, sut.AggregateQueryable.Count()); Assert.Contains(agg1, sut.AggregateQueryable); Assert.Contains(agg2, sut.AggregateQueryable); Assert.Contains(agg3, sut.AggregateQueryable); - sut = new InMemoryRepository(store, CurrentTenantIdHolder.Create(567), authorization); + sut = new InMemoryRepository( + store, + CurrentTenantIdHolder.Create(567), + authorization); Assert.Equal(2, sut.AggregateQueryable.Count()); Assert.Contains(agg4, sut.AggregateQueryable); Assert.Contains(agg5, sut.AggregateQueryable); @@ -312,13 +364,17 @@ public void ReturnsOnlyItemsFromMyTenant() public void ThrowsOnAddWhenTenantIdIsEmpty() { var authorization = A.Fake>(); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(null), + authorization); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); - Assert.Throws(() => sut.Add(new TheAggregateRoot.TestAggregateRoot(77, "whatever"))); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); + Assert.Throws( + () => sut.Add(new TheAggregateRoot.TestAggregateRoot(77, "whatever"))); // even when I don't have permissions A.CallTo(() => authorization.HasAccessExpression).Returns(agg => false); @@ -330,29 +386,37 @@ public void ThrowsOnAddWhenTenantIdIsEmpty() public void ThrowsOnAddRangeWhenTenantIdIsEmpty() { var authorization = A.Fake>(); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(null), + authorization); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); - Assert.Throws(() => sut.Add(new TheAggregateRoot.TestAggregateRoot(77, "whatever"))); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); + Assert.Throws( + () => sut.Add(new TheAggregateRoot.TestAggregateRoot(77, "whatever"))); // even when I don't have permissions A.CallTo(() => authorization.HasAccessExpression).Returns(agg => false); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); - Assert.Throws(() => sut.AddRange(new[] {new TheAggregateRoot.TestAggregateRoot(78, "whatever")})); + Assert.Throws( + () => sut.AddRange(new[] { new TheAggregateRoot.TestAggregateRoot(78, "whatever") })); } [Fact] public void ThrowsOnAddWhenUnauthorized() { var authorization = A.Fake>(); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); Assert.Throws(() => sut.Add(new TheAggregateRoot.TestAggregateRoot(44, "whatever"))); } @@ -361,13 +425,17 @@ public void ThrowsOnAddWhenUnauthorized() public void ThrowsOnAddRangeWhenUnauthorized() { var authorization = A.Fake>(); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(false); - Assert.Throws(() => sut.AddRange(new[] {new TheAggregateRoot.TestAggregateRoot(44, "whatever")})); + Assert.Throws( + () => sut.AddRange(new[] { new TheAggregateRoot.TestAggregateRoot(44, "whatever") })); } [Fact] @@ -375,15 +443,18 @@ public void ThrowsOnDeleteWhenTenantDoesNotMatch() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 999}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 999 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); @@ -398,13 +469,16 @@ public void ThrowsOnDeleteWhenTenantIdHolderIsEmpty() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(null), + authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") {TenantId = 234}; + var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); Assert.Throws(() => sut.Delete(agg1)); @@ -415,14 +489,17 @@ public void ThrowsOnDeleteWhenUnauthorized() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); A.CallTo(() => authorization.CanDelete(A._)).Returns(false); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") {TenantId = 234}; + var agg1 = new TheAggregateRoot.TestAggregateRoot(12123123, "whatever") { TenantId = 234 }; sut.Store.Add(agg1.Id, agg1); Assert.Throws(() => sut.Delete(agg1)); @@ -433,22 +510,25 @@ public void ThrowsOnResolveWhenTenantDoesNotMatch() { var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); - A.CallTo(() => authorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => authorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => authorization.CanCreate(A._)).Returns(true); - var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), - authorization); - var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") {TenantId = 234}; - var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") {TenantId = 234}; - var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") {TenantId = 234}; - var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") {TenantId = 999}; + var sut = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(234), + authorization); + var agg1 = new TheAggregateRoot.TestAggregateRoot(23, "whatever") { TenantId = 234 }; + var agg2 = new TheAggregateRoot.TestAggregateRoot(24, "whatever") { TenantId = 234 }; + var agg3 = new TheAggregateRoot.TestAggregateRoot(25, "whatever") { TenantId = 234 }; + var agg4 = new TheAggregateRoot.TestAggregateRoot(26, "whatever") { TenantId = 999 }; sut.Store.Add(agg1.Id, agg1); sut.Store.Add(agg2.Id, agg2); sut.Store.Add(agg3.Id, agg3); sut.Store.Add(agg4.Id, agg4); - Assert.Throws(() => sut.Resolve(new[] {23, 24, 25, 26})); + Assert.Throws(() => sut.Resolve(new[] { 23, 24, 25, 26 })); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs similarity index 95% rename from tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs rename to src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs index 3d975d38..4a4359fc 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs +++ b/src/abstractions/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs @@ -21,12 +21,12 @@ public void IsConsideredEqualWhenAllPropertiesAreEqual() Assert.True(myDumbValueObject1.Equals(myDumbValueObject2)); Assert.True(myDumbValueObject2.Equals(myDumbValueObject1)); Assert.True(Equals(myDumbValueObject1, myDumbValueObject2)); - + // attention! R# warns you, though // ReSharper disable once PossibleUnintendedReferenceComparison Assert.False(myValueObject1 == myValueObject2); } - + [Fact] public void IsNotConsideredEqualWhenOnePropertyDoesNotMatch() { @@ -35,13 +35,13 @@ public void IsNotConsideredEqualWhenOnePropertyDoesNotMatch() Assert.False(myValueObject1.Equals(myValueObject2)); Assert.False(myValueObject2.Equals(myValueObject1)); Assert.False(Equals(myValueObject1, myValueObject2)); - + object myDumbValueObject1 = myValueObject1; object myDumbValueObject2 = myValueObject2; Assert.False(myDumbValueObject1.Equals(myDumbValueObject2)); Assert.False(myDumbValueObject2.Equals(myDumbValueObject1)); Assert.False(Equals(myDumbValueObject1, myDumbValueObject2)); - + var myValueObject3 = new MyValueObject(333, "gnarfe"); var myValueObject4 = new MyValueObject(333, "gnarfo"); Assert.False(myValueObject3.Equals(myValueObject4)); @@ -55,16 +55,16 @@ public void CanBeCompared() var myValueObject1 = new MyValueObject(333, "gnarf"); var myValueObject2 = new MyValueObject(334, "gnarf"); var myValueObject3 = new MyValueObject(334, "gnarf"); - + Assert.True(myValueObject1.CompareTo(myValueObject2) == -1); Assert.True(myValueObject2.CompareTo(myValueObject1) == 1); Assert.True(myValueObject2.CompareTo(myValueObject3) == 0); Assert.True(myValueObject3.CompareTo(myValueObject2) == 0); - + object myDumbValueObject1 = myValueObject1; object myDumbValueObject2 = myValueObject2; object myDumbValueObject3 = myValueObject3; - + Assert.True(myValueObject1.CompareTo(myDumbValueObject2) == -1); Assert.True(myValueObject2.CompareTo(myDumbValueObject1) == 1); Assert.True(myValueObject2.CompareTo(myDumbValueObject3) == 0); @@ -78,26 +78,27 @@ public void DoesNotEqualNull() Assert.False(myValueObject1.Equals(null)); Assert.False(Equals(myValueObject1, null)); } - + [Fact] public void CanCompareToNull() { var myValueObject1 = new MyValueObject(333, "gnarf"); - Assert.Equal(1,myValueObject1.CompareTo(null)); + Assert.Equal(1, myValueObject1.CompareTo(null)); } + private class MyValueObject : ComparableValueObject { - private int Order { get; } - private string Name { get; } - - public MyValueObject(int order, string name) { Order = order; Name = name; } - + + private int Order { get; } + + private string Name { get; } + protected override IEnumerable GetEqualityComponents() { yield return Order; @@ -110,4 +111,4 @@ protected override IEnumerable GetComparableComponents() } } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs b/src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs similarity index 83% rename from tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs rename to src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs index d7571cca..b86660fc 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs +++ b/src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs @@ -9,15 +9,6 @@ namespace Backend.Fx.Tests.ConfigurationSettings { public class TheSetting { - [UsedImplicitly] - public class TestSettingsService : SettingsService - { - public TestSettingsService(IEntityIdGenerator idGenerator, IRepository settingRepository) - : base("Test", idGenerator, settingRepository, new SettingSerializerFactory()) - { - } - } - [Fact] public void CanStoreBoolean() { @@ -25,7 +16,7 @@ public void CanStoreBoolean() var sut = new Setting(3, "key"); sut.SetValue(new BooleanSerializer(), booleanValue); Assert.Equal("True", sut.SerializedValue); - var booleanValueRead = sut.GetValue(new BooleanSerializer()); + bool? booleanValueRead = sut.GetValue(new BooleanSerializer()); Assert.Equal(booleanValue, booleanValueRead); } @@ -36,7 +27,7 @@ public void CanStoreDateTime() var sut = new Setting(9, "key"); sut.SetValue(new DateTimeSerializer(), dateTimeValue); Assert.Equal("1987-04-22T23:12:11.0000000", sut.SerializedValue); - var dateTimeValueRead = sut.GetValue(new DateTimeSerializer()); + DateTime? dateTimeValueRead = sut.GetValue(new DateTimeSerializer()); Assert.Equal(dateTimeValue, dateTimeValueRead); } @@ -47,7 +38,7 @@ public void CanStoreDouble() var sut = new Setting(5, "key"); sut.SetValue(new DoubleSerializer(), doubleValue); Assert.Equal("2354.2341234", sut.SerializedValue); - var doubleeanValueRead = sut.GetValue(new DoubleSerializer()); + double? doubleeanValueRead = sut.GetValue(new DoubleSerializer()); Assert.Equal(doubleValue, doubleeanValueRead); } @@ -58,7 +49,7 @@ public void CanStoreInt() var sut = new Setting(7, "key"); sut.SetValue(new IntegerSerializer(), intValue); Assert.Equal("235234", sut.SerializedValue); - var inteanValueRead = sut.GetValue(new IntegerSerializer()); + int? inteanValueRead = sut.GetValue(new IntegerSerializer()); Assert.Equal(intValue, inteanValueRead); } @@ -68,7 +59,7 @@ public void CanStoreNullBoolean() var sut = new Setting(4, "key"); sut.SetValue(new BooleanSerializer(), null); Assert.Null(sut.SerializedValue); - var booleanValueRead = sut.GetValue(new BooleanSerializer()); + bool? booleanValueRead = sut.GetValue(new BooleanSerializer()); Assert.Null(booleanValueRead); } @@ -78,7 +69,7 @@ public void CanStoreNullDateTime() var sut = new Setting(10, "key"); sut.SetValue(new DateTimeSerializer(), null); Assert.Null(sut.SerializedValue); - var dateTimeValueRead = sut.GetValue(new DateTimeSerializer()); + DateTime? dateTimeValueRead = sut.GetValue(new DateTimeSerializer()); Assert.Null(dateTimeValueRead); } @@ -88,7 +79,7 @@ public void CanStoreNullDouble() var sut = new Setting(6, "key"); sut.SetValue(new DoubleSerializer(), null); Assert.Null(sut.SerializedValue); - var doubleValueRead = sut.GetValue(new DoubleSerializer()); + double? doubleValueRead = sut.GetValue(new DoubleSerializer()); Assert.Null(doubleValueRead); } @@ -98,7 +89,7 @@ public void CanStoreNullInt() var sut = new Setting(8, "key"); sut.SetValue(new IntegerSerializer(), null); Assert.Null(sut.SerializedValue); - var intValueRead = sut.GetValue(new IntegerSerializer()); + int? intValueRead = sut.GetValue(new IntegerSerializer()); Assert.Null(intValueRead); } @@ -109,7 +100,7 @@ public void CanStoreNullString() var sut = new Setting(2, "key"); sut.SetValue(new StringSerializer(), stringValue); Assert.Equal(stringValue, sut.SerializedValue); - var stringValueRead = sut.GetValue(new StringSerializer()); + string stringValueRead = sut.GetValue(new StringSerializer()); Assert.Equal(stringValue, stringValueRead); } @@ -120,8 +111,17 @@ public void CanStoreString() var sut = new Setting(1, "key"); sut.SetValue(new StringSerializer(), stringValue); Assert.Equal(stringValue, sut.SerializedValue); - var stringValueRead = sut.GetValue(new StringSerializer()); + string stringValueRead = sut.GetValue(new StringSerializer()); Assert.Equal(stringValue, stringValueRead); } + + + [UsedImplicitly] + public class TestSettingsService : SettingsService + { + public TestSettingsService(IEntityIdGenerator idGenerator, IRepository settingRepository) + : base("Test", idGenerator, settingRepository, new SettingSerializerFactory()) + { } + } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs b/src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs similarity index 77% rename from tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs rename to src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs index 5b79e8aa..6549e60b 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs +++ b/src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs @@ -6,40 +6,40 @@ namespace Backend.Fx.Tests.ConfigurationSettings { public class TheSettingSerializerFactory { - private readonly SettingSerializerFactory _sut = new SettingSerializerFactory(); + private readonly SettingSerializerFactory _sut = new(); [Fact] public void ProvidesBooleanSerializerForNullableBool() { - var serializer = _sut.GetSerializer(); + ISettingSerializer serializer = _sut.GetSerializer(); Assert.IsType(serializer); } [Fact] public void ProvidesBooleanSerializerForNullableDateTime() { - var serializer = _sut.GetSerializer(); + ISettingSerializer serializer = _sut.GetSerializer(); Assert.IsType(serializer); } [Fact] public void ProvidesBooleanSerializerForNullableDouble() { - var serializer = _sut.GetSerializer(); + ISettingSerializer serializer = _sut.GetSerializer(); Assert.IsType(serializer); } [Fact] public void ProvidesBooleanSerializerForNullableInt() { - var serializer = _sut.GetSerializer(); + ISettingSerializer serializer = _sut.GetSerializer(); Assert.IsType(serializer); } [Fact] public void ProvidesBooleanSerializerForString() { - var serializer = _sut.GetSerializer(); + ISettingSerializer serializer = _sut.GetSerializer(); Assert.IsType(serializer); } @@ -67,4 +67,4 @@ public void ProvidesNoSerializerForInt() Assert.Throws(() => _sut.GetSerializer()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs b/src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs similarity index 90% rename from tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs rename to src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs index bba31cea..424303d8 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs +++ b/src/abstractions/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs @@ -13,42 +13,27 @@ namespace Backend.Fx.Tests.ConfigurationSettings { public class TheSettingsService { + private readonly IEntityIdGenerator _idGenerator; + + private readonly InMemoryRepository _settingRepository; + public TheSettingsService() { var settingAuthorization = A.Fake>(); A.CallTo(() => settingAuthorization.HasAccessExpression).Returns(setting => true); - A.CallTo(() => settingAuthorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); + A.CallTo(() => settingAuthorization.Filter(A>._)) + .ReturnsLazily((IQueryable q) => q); A.CallTo(() => settingAuthorization.CanCreate(A._)).Returns(true); _idGenerator = A.Fake(); var nextId = 1; A.CallTo(() => _idGenerator.NextId()).ReturnsLazily(() => nextId++); - _settingRepository = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(999), settingAuthorization); - } - - public class MySettingsService : SettingsService - { - public MySettingsService(IEntityIdGenerator idGenerator, IRepository repo) - : base("My", idGenerator, repo, new SettingSerializerFactory()) - { - } - - public int SmtpPort - { - get => ReadSetting(nameof(SmtpPort)) ?? 25; - set => WriteSetting(nameof(SmtpPort), value); - } - - public string SmtpHost - { - get => ReadSetting(nameof(SmtpHost)); - set => WriteSetting(nameof(SmtpHost), value); - } + _settingRepository = new InMemoryRepository( + new InMemoryStore(), + CurrentTenantIdHolder.Create(999), + settingAuthorization); } - private readonly InMemoryRepository _settingRepository; - private readonly IEntityIdGenerator _idGenerator; - [Fact] public void ReadsNonExistingSettingAsDefaultFromRepository() { @@ -83,13 +68,33 @@ public void ReadsSettingFromRepository() [Fact] public void StoresSettingsInRepository() { - var sut = new MySettingsService(_idGenerator, _settingRepository) {SmtpPort = 333}; + var sut = new MySettingsService(_idGenerator, _settingRepository) { SmtpPort = 333 }; Assert.Equal(333, sut.SmtpPort); - var settings = _settingRepository.GetAll(); + Setting[] settings = _settingRepository.GetAll(); Assert.Single(settings); Assert.Equal("333", settings[0].SerializedValue); Assert.Equal("My.SmtpPort", settings[0].Key); } + + + public class MySettingsService : SettingsService + { + public MySettingsService(IEntityIdGenerator idGenerator, IRepository repo) + : base("My", idGenerator, repo, new SettingSerializerFactory()) + { } + + public int SmtpPort + { + get => ReadSetting(nameof(SmtpPort)) ?? 25; + set => WriteSetting(nameof(SmtpPort), value); + } + + public string SmtpHost + { + get => ReadSetting(nameof(SmtpHost)); + set => WriteSetting(nameof(SmtpHost), value); + } + } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs b/src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs rename to src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs index b57810a4..3d9174c9 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs @@ -23,4 +23,4 @@ public void IsNotAuthenticated() Assert.False(new AnonymousIdentity().IsAuthenticated); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs b/src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs rename to src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs index 4f5b5b44..a4a80744 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs @@ -28,4 +28,4 @@ public void ReplacesCurrentIdentity() Assert.Equal("SYSTEM", currentIdentityHolder.Current.Name); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs b/src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs rename to src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs index 493f47a4..aabc586b 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs @@ -23,4 +23,4 @@ public void IsAuthenticated() Assert.True(new SystemIdentity().IsAuthenticated); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs b/src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs rename to src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs index f18eea00..c79a7f84 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs @@ -18,4 +18,4 @@ public void AllowsOverridingOfUtcNow() Assert.Equal(overriddenUtcNow, sut.UtcNow); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs b/src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs similarity index 88% rename from tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs rename to src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs index 16ad6745..8bb73c3f 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs @@ -10,12 +10,11 @@ public class TheFrozenClock [Fact] public void IsFrozen() { - IClock sut = new FrozenClock(new WallClock()); - DateTime systemUtcNow = sut.UtcNow; + var systemUtcNow = sut.UtcNow; Thread.Sleep(100); Assert.Equal(systemUtcNow, sut.UtcNow); Assert.NotEqual(DateTime.UtcNow, sut.UtcNow); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs b/src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs similarity index 87% rename from tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs rename to src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs index 9e8b2788..4b3ce64c 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs @@ -9,7 +9,8 @@ namespace Backend.Fx.Tests.Environment.DateAndTime { public class TheWallClock { - private readonly IEqualityComparer _tolerantDateTimeComparer = new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(10)); + private readonly IEqualityComparer _tolerantDateTimeComparer + = new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(10)); [Fact] public void IsTheSystemClock() @@ -23,4 +24,4 @@ public void IsTheSystemClock() Assert.Equal(DateTime.UtcNow, sut.UtcNow, _tolerantDateTimeComparer); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs similarity index 53% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs index f3773baa..2f463edf 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs @@ -10,36 +10,47 @@ namespace Backend.Fx.Tests.Environment.MultiTenancy { public class TheAllTenantBackendFxApplicationInvoker { + private readonly IBackendFxApplicationInvoker _invoker = A.Fake(); + + private readonly AllTenantBackendFxApplicationInvoker _sut; + private readonly ITenantIdProvider _tenantService = A.Fake(); + public TheAllTenantBackendFxApplicationInvoker() { _sut = new AllTenantBackendFxApplicationInvoker(_tenantService, _invoker); } - private readonly AllTenantBackendFxApplicationInvoker _sut; - private readonly ITenantIdProvider _tenantService = A.Fake(); - private readonly IBackendFxApplicationInvoker _invoker = A.Fake(); - [Fact] public void InvokesActionForAllTenants() { - var demoTenantIds = Enumerable.Range(0, 10).Select(i => new TenantId(i)).ToArray(); - var prodTenantIds = Enumerable.Range(10, 10).Select(i => new TenantId(i)).ToArray(); + TenantId[] demoTenantIds = Enumerable.Range(0, 10).Select(i => new TenantId(i)).ToArray(); + TenantId[] prodTenantIds = Enumerable.Range(10, 10).Select(i => new TenantId(i)).ToArray(); A.CallTo(() => _tenantService.GetActiveDemonstrationTenantIds()).Returns(demoTenantIds); A.CallTo(() => _tenantService.GetActiveProductionTenantIds()).Returns(prodTenantIds); _sut.Invoke(_ => { }); - foreach (TenantId tenantId in demoTenantIds) + foreach (var tenantId in demoTenantIds) { - A.CallTo(() => _invoker.Invoke(A>._, A._, A.That.IsSameAs(tenantId), A._)) - .MustHaveHappenedOnceExactly(); + A.CallTo( + () => _invoker.Invoke( + A>._, + A._, + A.That.IsSameAs(tenantId), + A._)) + .MustHaveHappenedOnceExactly(); } - - foreach (TenantId tenantId in prodTenantIds) + + foreach (var tenantId in prodTenantIds) { - A.CallTo(() => _invoker.Invoke(A>._, A._, A.That.IsSameAs(tenantId), A._)) - .MustHaveHappenedOnceExactly(); + A.CallTo( + () => _invoker.Invoke( + A>._, + A._, + A.That.IsSameAs(tenantId), + A._)) + .MustHaveHappenedOnceExactly(); } } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs index cca7733b..da090d79 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs @@ -20,4 +20,4 @@ public void ReplacesCurrentTenantId() Assert.Equal(345, currentTenantIdHolder.Current.Value); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheMultiTenantApplication.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheMultiTenantApplication.cs similarity index 69% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheMultiTenantApplication.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheMultiTenantApplication.cs index add4fb7a..2d176afe 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheMultiTenantApplication.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheMultiTenantApplication.cs @@ -1,7 +1,6 @@ using System.Threading; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; @@ -9,34 +8,31 @@ namespace Backend.Fx.Tests.Environment.MultiTenancy { public class TheMultiTenantApplication { - private readonly IBackendFxApplication _sut; - private readonly IModule _multiTenancyModule = A.Fake(); - private readonly ITenantService _tenantService = A.Fake(); private readonly IBackendFxApplication _application = A.Fake(); + private readonly IBackendFxApplication _sut; public TheMultiTenantApplication() { _sut = new MultiTenantApplication(_application); } - [Fact] public void DelegatesAllCalls() { // ReSharper disable once UnusedVariable - IBackendFxApplicationAsyncInvoker ai = _sut.AsyncInvoker; + var ai = _sut.AsyncInvoker; A.CallTo(() => _application.AsyncInvoker).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - ICompositionRoot cr = _sut.CompositionRoot; + var cr = _sut.CompositionRoot; A.CallTo(() => _application.CompositionRoot).MustHaveHappenedOnceOrMore(); // ReSharper disable once UnusedVariable - IBackendFxApplicationInvoker i = _sut.Invoker; + var i = _sut.Invoker; A.CallTo(() => _application.Invoker).MustHaveHappenedOnceOrMore(); // ReSharper disable once UnusedVariable - IMessageBus mb = _sut.MessageBus; + var mb = _sut.MessageBus; A.CallTo(() => _application.MessageBus).MustHaveHappenedOnceExactly(); _sut.BootAsync(); @@ -44,9 +40,6 @@ public void DelegatesAllCalls() _sut.Dispose(); A.CallTo(() => _application.Dispose()).MustHaveHappenedOnceExactly(); - - _sut.WaitForBoot(); - A.CallTo(() => _application.WaitForBoot(A._, A._)).MustHaveHappenedOnceExactly(); - } + } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs similarity index 84% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs index 23af9d4f..c9f8aac0 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs @@ -2,7 +2,6 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Hacking; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; @@ -10,9 +9,9 @@ namespace Backend.Fx.Tests.Environment.MultiTenancy { public class TheSingleTenantApplication { + private readonly ICompositionRoot _compositionRoot = A.Fake(); private readonly IBackendFxApplication _sut; private readonly ITenantService _tenantService = A.Fake(); - private readonly ICompositionRoot _compositionRoot = A.Fake(); public TheSingleTenantApplication() { @@ -27,7 +26,8 @@ public TheSingleTenantApplication() public void CreatesTenantOnBootWhenNotExistent() { _sut.BootAsync(); - A.CallTo(() => _tenantService.CreateTenant(A._, A._, A._, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _tenantService.CreateTenant(A._, A._, A._, A._)) + .MustHaveHappenedOnceExactly(); } [Fact] @@ -35,10 +35,11 @@ public void CreatesNoTenantOnBootWhenExistent() { var tenant = new Tenant("single tenant", "", false); tenant.SetPrivate(t => t.Id, 1); - - A.CallTo(() => _tenantService.GetActiveTenants()).Returns(new[] {tenant}); + + A.CallTo(() => _tenantService.GetActiveTenants()).Returns(new[] { tenant }); _sut.BootAsync(); - A.CallTo(() => _tenantService.CreateTenant(A._, A._, A._, A._)).MustNotHaveHappened(); + A.CallTo(() => _tenantService.CreateTenant(A._, A._, A._, A._)) + .MustNotHaveHappened(); } [Fact] @@ -49,19 +50,19 @@ public void DelegatesAllCalls() Fake.ClearRecordedCalls(application); // ReSharper disable once UnusedVariable - IBackendFxApplicationAsyncInvoker ai = sut.AsyncInvoker; + var ai = sut.AsyncInvoker; A.CallTo(() => application.AsyncInvoker).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - ICompositionRoot cr = sut.CompositionRoot; + var cr = sut.CompositionRoot; A.CallTo(() => application.CompositionRoot).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - IBackendFxApplicationInvoker i = sut.Invoker; + var i = sut.Invoker; A.CallTo(() => application.Invoker).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - IMessageBus mb = sut.MessageBus; + var mb = sut.MessageBus; A.CallTo(() => application.MessageBus).MustHaveHappenedOnceExactly(); sut.BootAsync(); @@ -71,4 +72,4 @@ public void DelegatesAllCalls() A.CallTo(() => application.Dispose()).MustHaveHappenedOnceExactly(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs index ed996687..254cf563 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs @@ -24,4 +24,4 @@ public void InitializesCorrectly() Assert.True(tenant.IsDemoTenant); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs similarity index 99% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs index 37226de2..ca80c151 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs @@ -20,4 +20,4 @@ public void ThrowsOnAccessingTheValueWhenInitializedWithNull() Assert.Throws(() => sut.Value); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs similarity index 61% rename from tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs rename to src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs index 420a4247..96f58342 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs +++ b/src/abstractions/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs @@ -4,24 +4,60 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; namespace Backend.Fx.Tests.Environment.MultiTenancy { + public class TheTenantIdProvider + { + private readonly IMessageBus _messageBus = A.Fake(); + private readonly ITenantIdProvider _sut; + private readonly InMemoryTenantRepository _tenantRepository = new(); + + public TheTenantIdProvider() + { + var tenantService = new TenantService(_messageBus, _tenantRepository); + _sut = new TenantServiceTenantIdProvider(tenantService); + } + + [Fact] + public void GetsProductiveTenantIds() + { + Tenant[] tenants = Enumerable.Range(1, 7) + .Select(i => new Tenant("n" + i, "d" + i, i % 2 == 0)) + .ToArray(); + + foreach (var tenant in tenants) + { + tenant.State = TenantState.Active; + _tenantRepository.SaveTenant(tenant); + } + + TenantId[] tenantIds = tenants.Select(t => new TenantId(t.Id)).ToArray(); + TenantId[] demoTenantIds = tenants.Where(t => t.IsDemoTenant).Select(t => new TenantId(t.Id)).ToArray(); + TenantId[] prodTenantIds = tenants.Where(t => !t.IsDemoTenant).Select(t => new TenantId(t.Id)).ToArray(); + + Assert.Equal(tenantIds, _sut.GetActiveTenantIds()); + Assert.Equal(prodTenantIds, _sut.GetActiveProductionTenantIds()); + Assert.Equal(demoTenantIds, _sut.GetActiveDemonstrationTenantIds()); + } + } + + public class TheTenantService { + private readonly IMessageBus _messageBus = A.Fake(); + + private readonly ITenantService _sut; + private readonly InMemoryTenantRepository _tenantRepository = new(); + public TheTenantService() { _sut = new TenantService(_messageBus, _tenantRepository); } - private readonly ITenantService _sut; - private readonly IMessageBus _messageBus = A.Fake(); - private readonly InMemoryTenantRepository _tenantRepository = new InMemoryTenantRepository(); - [Fact] public void CannotCreateTenantWithoutName() { @@ -38,28 +74,6 @@ public void CannotCreateTenantWithSameName() Assert.Throws(() => _sut.CreateTenant("N", "d", true)); } - [Fact] - public void GetsProductiveTenantIds() - { - var tenants = Enumerable.Range(1, 7) - .Select(i => new Tenant("n" + i, "d" + i, i % 2 == 0)) - .ToArray(); - - foreach (Tenant tenant in tenants) - { - tenant.State = TenantState.Active; - _tenantRepository.SaveTenant(tenant); - } - - var tenantIds = tenants.Select(t => new TenantId(t.Id)).ToArray(); - var demoTenantIds = tenants.Where(t => t.IsDemoTenant).Select(t => new TenantId(t.Id)).ToArray(); - var prodTenantIds = tenants.Where(t => !t.IsDemoTenant).Select(t => new TenantId(t.Id)).ToArray(); - - Assert.Equal(tenantIds, _sut.TenantIdProvider.GetActiveTenantIds()); - Assert.Equal(prodTenantIds, _sut.TenantIdProvider.GetActiveProductionTenantIds()); - Assert.Equal(demoTenantIds, _sut.TenantIdProvider.GetActiveDemonstrationTenantIds()); - } - [Fact] public void RaisesTenantActivatedEvent() { @@ -69,4 +83,4 @@ public void RaisesTenantActivatedEvent() Assert.True(ev.WaitOne(Debugger.IsAttached ? int.MaxValue : 10000)); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Exceptions/TheNotFOundException.cs b/src/abstractions/Backend.Fx.Tests/Exceptions/TheNotFOundException.cs similarity index 99% rename from tests/Backend.Fx.Tests/Exceptions/TheNotFOundException.cs rename to src/abstractions/Backend.Fx.Tests/Exceptions/TheNotFOundException.cs index cc6fb251..4a13a6fd 100644 --- a/tests/Backend.Fx.Tests/Exceptions/TheNotFOundException.cs +++ b/src/abstractions/Backend.Fx.Tests/Exceptions/TheNotFOundException.cs @@ -14,4 +14,4 @@ public void FillsNameAndIdProperties() Assert.Equal(4711, exception.Id); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs b/src/abstractions/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs similarity index 77% rename from tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs rename to src/abstractions/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs index 9c9f51b6..962f25a5 100644 --- a/tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs +++ b/src/abstractions/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs @@ -9,7 +9,7 @@ public class TheUnprocessableExceptionBuilder [Fact] public void AddsExceptionWhenAggregateIsNull() { - IExceptionBuilder sut = UnprocessableException.UseBuilder(); + var sut = UnprocessableException.UseBuilder(); sut.AddNotFoundWhenNull(1111, null); Assert.Throws(() => sut.Dispose()); } @@ -17,7 +17,7 @@ public void AddsExceptionWhenAggregateIsNull() [Fact] public void AddsNoExceptionWhenAggregateIsNotNull() { - IExceptionBuilder sut = UnprocessableException.UseBuilder(); + var sut = UnprocessableException.UseBuilder(); sut.AddNotFoundWhenNull(1111, new TheAggregateRoot.TestAggregateRoot(12345, "gaga")); sut.Dispose(); } @@ -25,7 +25,7 @@ public void AddsNoExceptionWhenAggregateIsNotNull() [Fact] public void DoesNotThrowExceptionWhenNotAddingConditionalError() { - IExceptionBuilder sut = UnprocessableException.UseBuilder(); + var sut = UnprocessableException.UseBuilder(); sut.AddIf(false, "something is broken"); sut.Dispose(); } @@ -33,7 +33,7 @@ public void DoesNotThrowExceptionWhenNotAddingConditionalError() [Fact] public void ThrowsExceptionWhenAddingConditionalError() { - IExceptionBuilder sut = UnprocessableException.UseBuilder(); + var sut = UnprocessableException.UseBuilder(); sut.AddIf(true, "something is broken"); Assert.Throws(() => sut.Dispose()); } @@ -41,9 +41,9 @@ public void ThrowsExceptionWhenAddingConditionalError() [Fact] public void ThrowsExceptionWhenAddingError() { - IExceptionBuilder sut = UnprocessableException.UseBuilder(); + var sut = UnprocessableException.UseBuilder(); sut.Add("something is broken"); Assert.Throws(() => sut.Dispose()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs b/src/abstractions/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs similarity index 93% rename from tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs rename to src/abstractions/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs index 8bd3ad7f..8ce1cf3e 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs +++ b/src/abstractions/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs @@ -12,7 +12,7 @@ public void CanConvertToUnixEpochDate() Assert.Equal(0L, new DateTime(1970, 1, 1).ToUnixEpochDate()); Assert.Equal(1495675672L, new DateTime(2017, 5, 25, 1, 27, 52).ToUnixEpochDate()); Assert.Equal(int.MaxValue, new DateTime(2038, 1, 19, 3, 14, 7).ToUnixEpochDate()); - Assert.Equal((long) int.MaxValue + 1, new DateTime(2038, 1, 19, 3, 14, 8).ToUnixEpochDate()); + Assert.Equal((long)int.MaxValue + 1, new DateTime(2038, 1, 19, 3, 14, 8).ToUnixEpochDate()); } [Fact] @@ -36,4 +36,4 @@ public void CanGetWeekDay() Assert.Equal(new DateTime(2017, 05, 28), dt.GetWeekDay(DayOfWeek.Sunday)); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs b/src/abstractions/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs similarity index 92% rename from tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs rename to src/abstractions/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs index 2cd33b54..95fb5963 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs +++ b/src/abstractions/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs @@ -7,18 +7,19 @@ namespace Backend.Fx.Tests.Extensions { public class TheEnumerableEx { - private class Item - { - public bool Touched { get; set; } - } - [Fact] public void ExecutesActionForAll() { - IEnumerable enumerable = Enumerable.Range(0, 100).Select(i => new Item()).ToArray(); + IEnumerable enumerable = Enumerable.Range(0, 100).Select(_ => new Item()).ToArray(); enumerable.ForAll(itm => itm.Touched = true); Assert.All(enumerable, itm => Assert.True(itm.Touched)); } + + + private class Item + { + public bool Touched { get; set; } + } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs b/src/abstractions/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs similarity index 99% rename from tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs rename to src/abstractions/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs index d4e83889..0ac47688 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs +++ b/src/abstractions/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs @@ -11,6 +11,7 @@ public enum AnEnum Three } + public class TheStringEnumUtil { [Theory] @@ -36,4 +37,4 @@ public void ThrowsOnInvalidString() Assert.Throws(() => "whatever".Parse()); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx.Tests/Logging/TheExceptionLogger.cs b/src/abstractions/Backend.Fx.Tests/Logging/TheExceptionLogger.cs new file mode 100644 index 00000000..9c6da325 --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/Logging/TheExceptionLogger.cs @@ -0,0 +1,46 @@ +using System; +using Backend.Fx.Exceptions; +using Backend.Fx.Logging; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.Tests.Logging +{ + public class TheExceptionLogger + { + private readonly ILogger _logger = A.Fake(); + private readonly ExceptionLogger _sut; + + public TheExceptionLogger() + { + _sut = new ExceptionLogger(_logger); + } + + [Fact] + public void LogsClientExceptionAsWarning() + { + _sut.LogException(new ClientException("The message")); + + A.CallTo(() => _logger.Error(A._)).MustNotHaveHappened(); + A.CallTo(() => _logger.Warn(A._)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void LogsDerivatesOfClientExceptionAsWarning() + { + _sut.LogException(new UnprocessableException("The message")); + + A.CallTo(() => _logger.Error(A._)).MustNotHaveHappened(); + A.CallTo(() => _logger.Warn(A._)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void LogsOtherExceptionAsError() + { + _sut.LogException(new InvalidOperationException("The message")); + + A.CallTo(() => _logger.Warn(A._)).MustNotHaveHappened(); + A.CallTo(() => _logger.Error(A._)).MustHaveHappenedOnceExactly(); + } + } +} diff --git a/src/abstractions/Backend.Fx.Tests/Logging/TheLogManager.cs b/src/abstractions/Backend.Fx.Tests/Logging/TheLogManager.cs new file mode 100644 index 00000000..0d2263f8 --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/Logging/TheLogManager.cs @@ -0,0 +1,83 @@ +using System; +using System.Security; +using Backend.Fx.Logging; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.Tests.Logging +{ + public class TheDefaultLogManager + { + public TheDefaultLogManager() + { + // it's a static variable, ensure to reset it to default now: + LogManager.Initialize(new DebugLoggerFactory()); + } + + [Fact] + public void DoesNotThrowOnZeroConfig() + { + Exception ex = new SecurityException("very bad"); + var msg = "the log message"; + ILogger[] loggers = + { + LogManager.Create(), + LogManager.Create(typeof(TheLogManager)), + LogManager.Create("Backend.Fx.Tests.Logging.TheLogManager") + }; + + foreach (var logger in loggers) + { + logger.Fatal(ex); + logger.Fatal(ex, msg); + logger.Fatal(msg); + logger.Error(ex); + logger.Error(ex, msg); + logger.Error(msg); + logger.Warn(ex); + logger.Warn(ex, msg); + logger.Warn(msg); + logger.Info(ex); + logger.Info(ex, msg); + logger.Info(msg); + logger.Debug(ex); + logger.Debug(ex, msg); + logger.Debug(msg); + logger.Trace(ex); + logger.Trace(ex, msg); + logger.Trace(msg); + + logger.TraceDuration(msg).Dispose(); + logger.DebugDuration(msg).Dispose(); + logger.InfoDuration(msg).Dispose(); + } + } + } + + + public class TheLogManager + { + private readonly ILoggerFactory _loggerFactory = A.Fake(); + + public TheLogManager() + { + var logger = A.Fake(); + A.CallTo(() => _loggerFactory.Create()).Returns(logger); + LogManager.Initialize(_loggerFactory); + } + + [Fact] + public void CanBeginActivity() + { + LogManager.BeginActivity(); + A.CallTo(() => _loggerFactory.BeginActivity(A._)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void CanShutdown() + { + LogManager.Shutdown(); + A.CallTo(() => _loggerFactory.Shutdown()).MustHaveHappenedOnceExactly(); + } + } +} diff --git a/tests/Backend.Fx.Tests/Logging/TheLogger.cs b/src/abstractions/Backend.Fx.Tests/Logging/TheLogger.cs similarity index 58% rename from tests/Backend.Fx.Tests/Logging/TheLogger.cs rename to src/abstractions/Backend.Fx.Tests/Logging/TheLogger.cs index 4a5f2c72..cb7ad004 100644 --- a/tests/Backend.Fx.Tests/Logging/TheLogger.cs +++ b/src/abstractions/Backend.Fx.Tests/Logging/TheLogger.cs @@ -7,25 +7,29 @@ namespace Backend.Fx.Tests.Logging { public class TheLogger { + private readonly InvalidOperationException _exception = new("whatever"); + + private readonly ILoggerFactory _loggerFactory; + public TheLogger() { _loggerFactory = A.Fake(); var logger = A.Fake(); A.CallTo(() => _loggerFactory.Create()).Returns(logger); - A.CallTo(() => logger.DebugDuration(A.Ignored)).ReturnsLazily((string activity) => new DurationLogger(s => logger.Debug(s), activity)); - A.CallTo(() => logger.InfoDuration(A.Ignored)).ReturnsLazily((string activity) => new DurationLogger(s => logger.Info(s), activity)); - A.CallTo(() => logger.TraceDuration(A.Ignored)).ReturnsLazily((string activity) => new DurationLogger(s => logger.Trace(s), activity)); + A.CallTo(() => logger.DebugDuration(A.Ignored)) + .ReturnsLazily((string activity) => new DurationLogger(s => logger.Debug(s), activity)); + A.CallTo(() => logger.InfoDuration(A.Ignored)) + .ReturnsLazily((string activity) => new DurationLogger(s => logger.Info(s), activity)); + A.CallTo(() => logger.TraceDuration(A.Ignored)) + .ReturnsLazily((string activity) => new DurationLogger(s => logger.Trace(s), activity)); } - private readonly ILoggerFactory _loggerFactory; - private readonly InvalidOperationException _exception = new InvalidOperationException("whatever"); - [Fact] public void CanLogDebug() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Debug(msg); A.CallTo(() => logger.Debug(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); @@ -35,28 +39,33 @@ public void CanLogDebug() public void CanLogDebugDuration() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.DebugDuration(msg).Dispose(); A.CallTo(() => logger.Debug(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); - A.CallTo(() => logger.Debug(A.That.Matches(s => s.StartsWith(msg + " - Duration:")))).MustHaveHappenedOnceExactly(); + A.CallTo(() => logger.Debug(A.That.Matches(s => s.StartsWith(msg + " - Duration:")))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogDebugWithException() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Debug(_exception, msg); - A.CallTo(() => logger.Debug(A.That.Matches(ex => ex == _exception), A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); + A.CallTo( + () => logger.Debug( + A.That.Matches(ex => ex == _exception), + A.That.Matches(s => s == msg))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogError() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Error(msg); A.CallTo(() => logger.Error(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); @@ -66,17 +75,21 @@ public void CanLogError() public void CanLogErrorWithException() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Error(_exception, msg); - A.CallTo(() => logger.Error(A.That.Matches(ex => ex == _exception), A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); + A.CallTo( + () => logger.Error( + A.That.Matches(ex => ex == _exception), + A.That.Matches(s => s == msg))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogFatal() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Fatal(msg); A.CallTo(() => logger.Fatal(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); @@ -86,17 +99,21 @@ public void CanLogFatal() public void CanLogFatalWithException() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Fatal(_exception, msg); - A.CallTo(() => logger.Fatal(A.That.Matches(ex => ex == _exception), A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); + A.CallTo( + () => logger.Fatal( + A.That.Matches(ex => ex == _exception), + A.That.Matches(s => s == msg))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogInfo() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Info(msg); A.CallTo(() => logger.Info(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); @@ -106,28 +123,33 @@ public void CanLogInfo() public void CanLogInfoDuration() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.InfoDuration(msg).Dispose(); A.CallTo(() => logger.Info(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); - A.CallTo(() => logger.Info(A.That.Matches(s => s.StartsWith(msg + " - Duration:")))).MustHaveHappenedOnceExactly(); + A.CallTo(() => logger.Info(A.That.Matches(s => s.StartsWith(msg + " - Duration:")))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogInfoWithException() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Info(_exception, msg); - A.CallTo(() => logger.Info(A.That.Matches(ex => ex == _exception), A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); + A.CallTo( + () => logger.Info( + A.That.Matches(ex => ex == _exception), + A.That.Matches(s => s == msg))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogTrace() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Trace(msg); A.CallTo(() => logger.Trace(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); @@ -137,28 +159,33 @@ public void CanLogTrace() public void CanLogTraceDuration() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.TraceDuration(msg).Dispose(); A.CallTo(() => logger.Trace(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); - A.CallTo(() => logger.Trace(A.That.Matches(s => s.StartsWith(msg + " - Duration:")))).MustHaveHappenedOnceExactly(); + A.CallTo(() => logger.Trace(A.That.Matches(s => s.StartsWith(msg + " - Duration:")))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogTraceWithException() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Trace(_exception, msg); - A.CallTo(() => logger.Trace(A.That.Matches(ex => ex == _exception), A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); + A.CallTo( + () => logger.Trace( + A.That.Matches(ex => ex == _exception), + A.That.Matches(s => s == msg))) + .MustHaveHappenedOnceExactly(); } [Fact] public void CanLogWarning() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Warn(msg); A.CallTo(() => logger.Warn(A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); @@ -168,10 +195,14 @@ public void CanLogWarning() public void CanLogWarnWithException() { var msg = "the log message"; - ILogger logger = _loggerFactory.Create(); + var logger = _loggerFactory.Create(); logger.Warn(_exception, msg); - A.CallTo(() => logger.Warn(A.That.Matches(ex => ex == _exception), A.That.Matches(s => s == msg))).MustHaveHappenedOnceExactly(); + A.CallTo( + () => logger.Warn( + A.That.Matches(ex => ex == _exception), + A.That.Matches(s => s == msg))) + .MustHaveHappenedOnceExactly(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs b/src/abstractions/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs similarity index 82% rename from tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs index da07574a..f2c0ce3f 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs @@ -7,14 +7,16 @@ namespace Backend.Fx.Tests.Patterns.Authorization { public class TheAllowAllImplementation { - private readonly IAggregateAuthorization _sut = new AllowAll(); - private readonly TheAggregateRoot.TestAggregateRoot _testAggregateRoot = new TheAggregateRoot.TestAggregateRoot(1,"e"); + private readonly IAggregateAuthorization _sut + = new AllowAll(); + + private readonly TheAggregateRoot.TestAggregateRoot _testAggregateRoot = new(1, "e"); [Fact] public void AllowsAccess() { Assert.True(_sut.HasAccessExpression.Compile().Invoke(_testAggregateRoot)); - Assert.Contains(_testAggregateRoot, _sut.Filter(new[] {_testAggregateRoot}.AsQueryable())); + Assert.Contains(_testAggregateRoot, _sut.Filter(new[] { _testAggregateRoot }.AsQueryable())); } [Fact] @@ -22,17 +24,17 @@ public void AllowsCreation() { Assert.True(_sut.CanCreate(_testAggregateRoot)); } - + [Fact] public void AllowsModification() { Assert.True(_sut.CanModify(_testAggregateRoot)); } - + [Fact] public void AllowsDeletion() { Assert.True(_sut.CanDelete(_testAggregateRoot)); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs b/src/abstractions/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs similarity index 78% rename from tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs index 6807eac3..e92be15f 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs @@ -7,15 +7,16 @@ namespace Backend.Fx.Tests.Patterns.Authorization { public class TheDenyAllImplementation { - private readonly IAggregateAuthorization _sut = new DenyAll(); - private readonly TheAggregateRoot.TestAggregateRoot _testAggregateRoot = new TheAggregateRoot.TestAggregateRoot(1,"e"); + private readonly IAggregateAuthorization _sut + = new DenyAll(); + + private readonly TheAggregateRoot.TestAggregateRoot _testAggregateRoot = new(1, "e"); - [Fact] public void DeniesAccess() { Assert.False(_sut.HasAccessExpression.Compile().Invoke(_testAggregateRoot)); - Assert.Empty(_sut.Filter(new[] {_testAggregateRoot}.AsQueryable())); + Assert.Empty(_sut.Filter(new[] { _testAggregateRoot }.AsQueryable())); } [Fact] @@ -23,17 +24,17 @@ public void DeniesCreation() { Assert.False(_sut.CanCreate(_testAggregateRoot)); } - + [Fact] public void DeniesModification() { Assert.False(_sut.CanModify(_testAggregateRoot)); } - + [Fact] public void DeniesDeletion() { Assert.False(_sut.CanDelete(_testAggregateRoot)); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs similarity index 68% rename from tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index 84e56ed0..7a3b9390 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -12,31 +12,35 @@ namespace Backend.Fx.Tests.Patterns.DataGeneration { public class TheDataGenerationContext { + private readonly IDemoDataGenerator[] _demoDataGenerators = + { new DemoDataGenerator1(), new DemoDataGenerator2() }; + + private readonly TenantId[] _demoTenants = { new(1), new(2) }; + private readonly IProductiveDataGenerator[] _prodDataGenerators = { new ProdDataGenerator1() }; + private readonly TenantId[] _prodTenants = { new(11), new(12) }; + + private readonly DataGenerationContext _sut; + public TheDataGenerationContext() { var fakes = new DiTestFakes(); - A.CallTo(() => fakes.InstanceProvider.GetInstances()).Returns(_demoDataGenerators.Concat(_prodDataGenerators.Cast()).ToArray()); + A.CallTo(() => fakes.InstanceProvider.GetInstances()) + .Returns(_demoDataGenerators.Concat(_prodDataGenerators.Cast()).ToArray()); var application = A.Fake(); A.CallTo(() => application.Invoker).Returns(fakes.Invoker); - A.CallTo(() => application.WaitForBoot(A._, A._)).Returns(true); - + var messageBus = new InMemoryMessageBus(); messageBus.ProvideInvoker(application.Invoker); - + var tenantIdProvider = A.Fake(); A.CallTo(() => tenantIdProvider.GetActiveDemonstrationTenantIds()).Returns(_demoTenants); A.CallTo(() => tenantIdProvider.GetActiveProductionTenantIds()).Returns(_prodTenants); - - _sut = new DataGenerationContext(fakes.CompositionRoot, - fakes.Invoker); - } - private readonly DataGenerationContext _sut; - private readonly IDemoDataGenerator[] _demoDataGenerators = {new DemoDataGenerator1(), new DemoDataGenerator2()}; - private readonly IProductiveDataGenerator[] _prodDataGenerators = {new ProdDataGenerator1()}; - private readonly TenantId[] _demoTenants = {new TenantId(1), new TenantId(2)}; - private readonly TenantId[] _prodTenants = {new TenantId(11), new TenantId(12)}; + _sut = new DataGenerationContext( + fakes.CompositionRoot, + fakes.Invoker); + } [Theory] [InlineData(true)] @@ -45,16 +49,25 @@ public void CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTenant) { _sut.SeedDataForTenant(new TenantId(123), isDemoTenant); - foreach (IProductiveDataGenerator dataGenerator in _prodDataGenerators) - A.CallTo(() => ((ProdDataGenerator) dataGenerator).Impl.Generate()).MustHaveHappenedOnceExactly(); + foreach (var dataGenerator in _prodDataGenerators) + { + A.CallTo(() => ((ProdDataGenerator)dataGenerator).Impl.Generate()).MustHaveHappenedOnceExactly(); + } - foreach (IDemoDataGenerator dataGenerator in _demoDataGenerators) + foreach (var dataGenerator in _demoDataGenerators) + { if (isDemoTenant) - A.CallTo(() => ((DemoDataGenerator) dataGenerator).Impl.Generate()).MustHaveHappenedOnceExactly(); + { + A.CallTo(() => ((DemoDataGenerator)dataGenerator).Impl.Generate()).MustHaveHappenedOnceExactly(); + } else - A.CallTo(() => ((DemoDataGenerator) dataGenerator).Impl.Generate()).MustNotHaveHappened(); + { + A.CallTo(() => ((DemoDataGenerator)dataGenerator).Impl.Generate()).MustNotHaveHappened(); + } + } } + private abstract class DemoDataGenerator : IDemoDataGenerator { public readonly IDemoDataGenerator Impl; @@ -72,13 +85,14 @@ public void Generate() } } + private class DemoDataGenerator1 : DemoDataGenerator - { - } + { } + private class DemoDataGenerator2 : DemoDataGenerator - { - } + { } + private abstract class ProdDataGenerator : IProductiveDataGenerator { @@ -97,8 +111,8 @@ public void Generate() } } + private class ProdDataGenerator1 : ProdDataGenerator - { - } + { } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs similarity index 50% rename from tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs index cdc77cac..055513be 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs @@ -1,10 +1,8 @@ -using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Hacking; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; @@ -12,77 +10,81 @@ namespace Backend.Fx.Tests.Patterns.DataGeneration { public class TheGenerateDataOnBootDecorator { + private readonly IDataGenerationContext _dataGenerationContext; + + private readonly GenerateDataOnBoot _sut; + private readonly ITenantIdProvider _tenantIdProvider; + public TheGenerateDataOnBootDecorator() { - _compositionRoot = A.Fake(); - _dataGenerationModule = A.Fake(); + var compositionRoot = A.Fake(); _dataGenerationContext = A.Fake(); _tenantIdProvider = A.Fake(); - + var backendFxApplication = A.Fake(); - A.CallTo(() => backendFxApplication.CompositionRoot).Returns(_compositionRoot); - _sut = new GenerateDataOnBoot(_tenantIdProvider, - _dataGenerationModule, backendFxApplication); + A.CallTo(() => backendFxApplication.CompositionRoot).Returns(compositionRoot); + _sut = new GenerateDataOnBoot(_tenantIdProvider, backendFxApplication); _sut.SetPrivate(f => f.DataGenerationContext, _dataGenerationContext); } - private readonly GenerateDataOnBoot _sut; - private readonly IModule _dataGenerationModule; - private readonly IDataGenerationContext _dataGenerationContext; - private readonly ICompositionRoot _compositionRoot; - private readonly ITenantIdProvider _tenantIdProvider; - [Fact] public void DelegatesAllOtherCalls() { var app = A.Fake(); - IBackendFxApplication sut = new GenerateDataOnBoot(A.Fake(), A.Fake(), app); + IBackendFxApplication sut = new GenerateDataOnBoot(A.Fake(), app); // ReSharper disable UnusedVariable - IBackendFxApplicationAsyncInvoker ai = sut.AsyncInvoker; + var ai = sut.AsyncInvoker; A.CallTo(() => app.AsyncInvoker).MustHaveHappenedOnceExactly(); - ICompositionRoot cr = sut.CompositionRoot; + var cr = sut.CompositionRoot; A.CallTo(() => app.CompositionRoot).MustHaveHappenedOnceOrMore(); sut.Dispose(); A.CallTo(() => app.Dispose()).MustHaveHappenedOnceExactly(); - IBackendFxApplicationInvoker i = sut.Invoker; + var i = sut.Invoker; A.CallTo(() => app.Invoker).MustHaveHappenedOnceOrMore(); - IMessageBus mb = sut.MessageBus; + var mb = sut.MessageBus; A.CallTo(() => app.MessageBus).MustHaveHappenedOnceExactly(); // ReSharper restore UnusedVariable } - [Fact] - public async Task RegistersDataGenerationModuleOnBoot() - { - await _sut.BootAsync(); - A.CallTo(() => _compositionRoot.RegisterModules(A.That.Contains(_dataGenerationModule))).MustHaveHappenedOnceExactly(); - } - [Fact] public async Task RunsDataGeneratorsOnBoot() { var tenantId1 = new TenantId(1); var tenantId2 = new TenantId(2); var tenantId3 = new TenantId(3); - var tenantId4 = new TenantId(4); - - A.CallTo(() => _tenantIdProvider.GetActiveDemonstrationTenantIds()).Returns(new[] {tenantId1, tenantId2}); - A.CallTo(() => _tenantIdProvider.GetActiveProductionTenantIds()).Returns(new[] {tenantId3, tenantId4}); - await _sut.BootAsync(); - A.CallTo(() => _dataGenerationContext.SeedDataForTenant(A.That.IsEqualTo(tenantId1), A.That.IsEqualTo(true))).MustHaveHappenedOnceExactly(); - A.CallTo(() => _dataGenerationContext.SeedDataForTenant(A.That.IsEqualTo(tenantId2), A.That.IsEqualTo(true))).MustHaveHappenedOnceExactly(); - A.CallTo(() => _dataGenerationContext.SeedDataForTenant(A.That.IsEqualTo(tenantId3), A.That.IsEqualTo(false))).MustHaveHappenedOnceExactly(); - A.CallTo(() => _dataGenerationContext.SeedDataForTenant(A.That.IsEqualTo(tenantId4), A.That.IsEqualTo(false))).MustHaveHappenedOnceExactly(); + var tenantId4 = new TenantId(4); - Assert.True(_sut.WaitForBoot(1000)); + A.CallTo(() => _tenantIdProvider.GetActiveDemonstrationTenantIds()).Returns(new[] { tenantId1, tenantId2 }); + A.CallTo(() => _tenantIdProvider.GetActiveProductionTenantIds()).Returns(new[] { tenantId3, tenantId4 }); + await _sut.BootAsync(); + A.CallTo( + () => _dataGenerationContext.SeedDataForTenant( + A.That.IsEqualTo(tenantId1), + A.That.IsEqualTo(true))) + .MustHaveHappenedOnceExactly(); + A.CallTo( + () => _dataGenerationContext.SeedDataForTenant( + A.That.IsEqualTo(tenantId2), + A.That.IsEqualTo(true))) + .MustHaveHappenedOnceExactly(); + A.CallTo( + () => _dataGenerationContext.SeedDataForTenant( + A.That.IsEqualTo(tenantId3), + A.That.IsEqualTo(false))) + .MustHaveHappenedOnceExactly(); + A.CallTo( + () => _dataGenerationContext.SeedDataForTenant( + A.That.IsEqualTo(tenantId4), + A.That.IsEqualTo(false))) + .MustHaveHappenedOnceExactly(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs similarity index 95% rename from tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs index 66a21843..db70365a 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs @@ -6,8 +6,11 @@ namespace Backend.Fx.Tests.Patterns.DataGeneration public class ADataGenerator : DataGenerator { public bool ShouldRunOverride { get; set; } + public int GenerateCoreCalled { get; private set; } + public int ShouldRunCalled { get; private set; } + public int InitializeCalled { get; private set; } public override int Priority => 12; @@ -29,9 +32,10 @@ protected override bool ShouldRun() } } + public class TheInitialDataGenerator { - private readonly ADataGenerator _sut = new ADataGenerator(); + private readonly ADataGenerator _sut = new(); [Fact] public void RespectsNegativeShouldRunMethodResult() @@ -52,4 +56,4 @@ public void RespectsPositiveShouldRunMethodResult() Assert.Equal(1, _sut.InitializeCalled); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs similarity index 83% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs index 6198d8c1..d01639e7 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs @@ -15,7 +15,8 @@ public class DiTestFakes public DiTestFakes() { - A.CallTo(() => InstanceProvider.GetInstance>()).Returns(CurrentCorrelationHolder); + A.CallTo(() => InstanceProvider.GetInstance>()) + .Returns(CurrentCorrelationHolder); A.CallTo(() => InstanceProvider.GetInstance>()).Returns(TenantIdHolder); A.CallTo(() => InstanceProvider.GetInstance>()).Returns(IdentityHolder); A.CallTo(() => InstanceProvider.GetInstance()).Returns(Operation); @@ -24,22 +25,29 @@ public DiTestFakes() A.CallTo(() => InjectionScope.InstanceProvider).Returns(InstanceProvider); A.CallTo(() => CompositionRoot.BeginScope()).Returns(InjectionScope); - A.CallTo(() => CompositionRoot.InfrastructureModule).Returns(InfrastructureModule); A.CallTo(() => Invoker.Invoke(A>._, A._, A._, A._)) - .Invokes((Action a, IIdentity i, TenantId t, Guid? g) => a.Invoke(InstanceProvider)); + .Invokes((Action a, IIdentity _, TenantId _, Guid? _) => a.Invoke(InstanceProvider)); } public ICurrentTHolder TenantIdHolder { get; } = A.Fake>(); + public ICurrentTHolder IdentityHolder { get; } = A.Fake>(); + public IOperation Operation { get; } = A.Fake(); + public ICompositionRoot CompositionRoot { get; } = A.Fake(); - public CurrentCorrelationHolder CurrentCorrelationHolder { get; } = new CurrentCorrelationHolder(); + + public CurrentCorrelationHolder CurrentCorrelationHolder { get; } = new(); + public IInjectionScope InjectionScope { get; } = A.Fake(); + public IExceptionLogger ExceptionLogger { get; } = A.Fake(); + public IInstanceProvider InstanceProvider { get; } = A.Fake(); + public IMessageBus MessageBus { get; } = A.Fake(); - public IInfrastructureModule InfrastructureModule { get; } = A.Fake(); + public IBackendFxApplicationInvoker Invoker { get; } = A.Fake(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs new file mode 100644 index 00000000..f00e9c15 --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Backend.Fx.Tests.Patterns.DependencyInjection +{ + public class SimulatedException : Exception + { + public SimulatedException() : base( + "This exception was intentionally thrown by the unit test. If you see this message unexpectedly, probably the exception handling is broken") + { } + } +} diff --git a/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs new file mode 100644 index 00000000..48ae9c26 --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -0,0 +1,81 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.EventAggregation.Integration; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.Tests.Patterns.DependencyInjection +{ + public class TheBackendFxApplication + { + private readonly DiTestFakes _fakes; + + private readonly IBackendFxApplication _sut; + + public TheBackendFxApplication() + { + _fakes = new DiTestFakes(); + + Func domainEventAggregatorFactory = () => null; + A.CallTo(() => _fakes.InstanceProvider.GetInstance()) + .ReturnsLazily(() => domainEventAggregatorFactory.Invoke()); + + Func messageBusScopeFactory = () => null; + A.CallTo(() => _fakes.InstanceProvider.GetInstance()) + .ReturnsLazily(() => messageBusScopeFactory.Invoke()); + + _sut = new BackendFxApplication(_fakes.CompositionRoot, _fakes.MessageBus, A.Fake()); + } + + [Fact] + public async Task CanWaitForBoot() + { + var bootTime = 200; + A.CallTo(() => _fakes.CompositionRoot.Verify()).Invokes(() => Thread.Sleep(bootTime)); + var sw = new Stopwatch(); + + sw.Start(); + await _sut.BootAsync(); + Assert.True(sw.ElapsedMilliseconds >= bootTime); + } + + [Fact] + public void DisposesCompositionRootOnDispose() + { + _sut.BootAsync(); + _sut.Dispose(); + A.CallTo(() => _fakes.CompositionRoot.Dispose()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void ProvidesExceptionLoggingAsyncInvoker() + { + Assert.IsType(_sut.AsyncInvoker); + } + + [Fact] + public void ProvidesExceptionLoggingInvoker() + { + Assert.IsType(_sut.Invoker); + } + + [Fact] + public void IntegratesWithMessageBus() + { + A.CallTo(() => _fakes.MessageBus.ProvideInvoker(A._)) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public void VerifiesCompositionRootOnBoot() + { + _sut.BootAsync(); + A.CallTo(() => _fakes.CompositionRoot.Verify()).MustHaveHappenedOnceExactly(); + } + } +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs similarity index 64% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs index 349a0fcf..4934e903 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs @@ -11,23 +11,24 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class TheBackendFxApplicationAsyncInvoker { + private readonly DiTestFakes _fakes; + + private readonly IBackendFxApplicationAsyncInvoker _sut; + public TheBackendFxApplicationAsyncInvoker() { _fakes = new DiTestFakes(); _sut = new BackendFxApplicationInvoker(_fakes.CompositionRoot); } - private readonly IBackendFxApplicationAsyncInvoker _sut; - private readonly DiTestFakes _fakes; - [Fact] public async Task BeginsNewScopeForEveryInvocation() { - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedTwiceExactly(); } @@ -35,11 +36,11 @@ public async Task BeginsNewScopeForEveryInvocation() [Fact] public async Task BeginsNewOperationForEveryInvocation() { - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -48,7 +49,11 @@ public async Task BeginsNewOperationForEveryInvocation() public async Task DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync( + async () => await _sut.InvokeAsync( + _ => Task.CompletedTask, + new AnonymousIdentity(), + new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -56,7 +61,11 @@ public async Task DoesNotCatchFrameworkExceptions() [Fact] public async Task DoesNotCatchOperationExceptions() { - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(ip => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync( + async () => await _sut.InvokeAsync( + _ => throw new SimulatedException(), + new AnonymousIdentity(), + new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -65,7 +74,7 @@ public async Task DoesNotCatchOperationExceptions() public async Task MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -73,28 +82,33 @@ public async Task MaintainsCorrelationIdOnInvocation() public async Task MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - await _sut.InvokeAsync(ip => Task.CompletedTask, identity, new TenantId(123)); - A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); + await _sut.InvokeAsync(_ => Task.CompletedTask, identity, new TenantId(123)); + A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))) + .MustHaveHappenedOnceExactly(); } [Fact] public async Task MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), tenantId); - A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), tenantId); + A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))) + .MustHaveHappenedOnceExactly(); } [Fact] public async Task ProvidesInstanceProviderForInvocation() { IInstanceProvider provided = null; - await _sut.InvokeAsync(ip => - { - provided = ip; - return Task.CompletedTask; - }, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync( + ip => + { + provided = ip; + return Task.CompletedTask; + }, + new AnonymousIdentity(), + new TenantId(111)); Assert.StrictEqual(_fakes.InstanceProvider, provided); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs similarity index 76% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs index c30dcbaa..d5c133b6 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs @@ -10,23 +10,24 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class TheBackendFxApplicationInvoker { + private readonly DiTestFakes _fakes; + + private readonly IBackendFxApplicationInvoker _sut; + public TheBackendFxApplicationInvoker() { _fakes = new DiTestFakes(); _sut = new BackendFxApplicationInvoker(_fakes.CompositionRoot); } - private readonly IBackendFxApplicationInvoker _sut; - private readonly DiTestFakes _fakes; - [Fact] public void BeginsNewScopeForEveryInvocation() { - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedOnceExactly(); - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedTwiceExactly(); } @@ -34,11 +35,11 @@ public void BeginsNewScopeForEveryInvocation() [Fact] public void BeginsNewOperationForEveryInvocation() { - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -47,7 +48,7 @@ public void BeginsNewOperationForEveryInvocation() public void DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - Assert.Throws(() => _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111))); + Assert.Throws(() => _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -55,7 +56,8 @@ public void DoesNotCatchFrameworkExceptions() [Fact] public void DoesNotCatchOperationExceptions() { - Assert.Throws(() => _sut.Invoke(ip => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + Assert.Throws( + () => _sut.Invoke(_ => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -64,7 +66,7 @@ public void DoesNotCatchOperationExceptions() public void MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(123), correlationId); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(123), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -72,19 +74,20 @@ public void MaintainsCorrelationIdOnInvocation() public void MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - _sut.Invoke(ip => { }, identity, new TenantId(123)); - A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); + _sut.Invoke(_ => { }, identity, new TenantId(123)); + A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))) + .MustHaveHappenedOnceExactly(); } [Fact] public void MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - _sut.Invoke(ip => { }, new AnonymousIdentity(), tenantId); - A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); + _sut.Invoke(_ => { }, new AnonymousIdentity(), tenantId); + A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))) + .MustHaveHappenedOnceExactly(); } - [Fact] public void ProvidesInstanceProviderForInvocation() { @@ -93,4 +96,4 @@ public void ProvidesInstanceProviderForInvocation() Assert.StrictEqual(_fakes.InstanceProvider, provided); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs similarity index 69% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs index 494686a8..93b0405d 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs @@ -2,7 +2,6 @@ using Backend.Fx.Environment.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; @@ -10,24 +9,31 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class TheBackendFxDbApplication { + private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter + = A.Fake(); + + private readonly IDatabaseBootstrapper _databaseBootstrapper = A.Fake(); + + private readonly DiTestFakes _fakes = new(); + private readonly IBackendFxApplication _sut; + public TheBackendFxDbApplication() { - IBackendFxApplication application = new BackendFxApplication(_fakes.CompositionRoot, _fakes.MessageBus, A.Fake()); + IBackendFxApplication application = new BackendFxApplication( + _fakes.CompositionRoot, + _fakes.MessageBus, + A.Fake()); _sut = new BackendFxDbApplication(_databaseBootstrapper, _databaseAvailabilityAwaiter, application); } - private readonly DiTestFakes _fakes = new DiTestFakes(); - private readonly IBackendFxApplication _sut; - private readonly IDatabaseBootstrapper _databaseBootstrapper = A.Fake(); - private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter = A.Fake(); - [Fact] public void CallsDatabaseBootExtensionPointsOnBoot() { A.CallTo(() => _databaseAvailabilityAwaiter.WaitForDatabase(A._)).MustNotHaveHappened(); A.CallTo(() => _databaseBootstrapper.EnsureDatabaseExistence()).MustNotHaveHappened(); _sut.BootAsync(); - A.CallTo(() => _databaseAvailabilityAwaiter.WaitForDatabase(A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _databaseAvailabilityAwaiter.WaitForDatabase(A._)) + .MustHaveHappenedOnceExactly(); A.CallTo(() => _databaseBootstrapper.EnsureDatabaseExistence()).MustHaveHappenedOnceExactly(); } @@ -41,25 +47,26 @@ public void CallsDatabaseBootstrapperDisposeOnDispose() [Fact] public void DelegatesAllCalls() { - var application =A.Fake(); - var sut = new BackendFxDbApplication(A.Fake(), - A.Fake(), - application); - + var application = A.Fake(); + var sut = new BackendFxDbApplication( + A.Fake(), + A.Fake(), + application); + // ReSharper disable once UnusedVariable - IBackendFxApplicationAsyncInvoker ai = sut.AsyncInvoker; + var ai = sut.AsyncInvoker; A.CallTo(() => application.AsyncInvoker).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - ICompositionRoot cr = sut.CompositionRoot; + var cr = sut.CompositionRoot; A.CallTo(() => application.CompositionRoot).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - IBackendFxApplicationInvoker i = sut.Invoker; + var i = sut.Invoker; A.CallTo(() => application.Invoker).MustHaveHappenedOnceExactly(); // ReSharper disable once UnusedVariable - IMessageBus mb = sut.MessageBus; + var mb = sut.MessageBus; A.CallTo(() => application.MessageBus).MustHaveHappenedOnceExactly(); sut.BootAsync(); @@ -67,11 +74,6 @@ public void DelegatesAllCalls() sut.Dispose(); A.CallTo(() => application.Dispose()).MustHaveHappenedOnceExactly(); - - sut.WaitForBoot(); - A.CallTo(() => application.WaitForBoot(A._, A._)).MustHaveHappenedOnceExactly(); } - - } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs similarity index 89% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs index b88d27a0..f35e59a2 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs @@ -6,7 +6,7 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class TheCorrelation { - private readonly Correlation _sut = new Correlation(); + private readonly Correlation _sut = new(); [Fact] public void CanResume() @@ -22,4 +22,4 @@ public void InitializesWithId() Assert.NotEqual(Guid.Empty, _sut.Id); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs similarity index 84% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs index de15fba6..abe10170 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs @@ -6,8 +6,8 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class TheCurrentTHolder { + private readonly TenantId _instance2 = new(2); private readonly ICurrentTHolder _sut = new CurrentTenantIdHolder(); - private readonly TenantId _instance2 = new TenantId(2); [Fact] public void CanReplaceCurrent() @@ -19,8 +19,8 @@ public void CanReplaceCurrent() [Fact] public void HoldsCurrent() { - TenantId current = _sut.Current; + var current = _sut.Current; Assert.False(current.HasValue); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs index 1304c108..fe262386 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs @@ -9,6 +9,21 @@ public class TheInjectionScope { private readonly IInstanceProvider _instanceProvider = A.Fake(); + [Fact] + public void InitializesWithSequenceNumber() + { + var injectionScope = new TestInjectionScope(42, _instanceProvider); + Assert.Equal(42, injectionScope.SequenceNumber); + } + + [Fact] + public void KeepsInstanceProvider() + { + var injectionScope = new TestInjectionScope(42, _instanceProvider); + Assert.Equal(_instanceProvider, injectionScope.InstanceProvider); + } + + private class TestInjectionScope : InjectionScope { public TestInjectionScope(int sequenceNumber, IInstanceProvider instanceProvider) : base(sequenceNumber) @@ -23,19 +38,5 @@ public override void Dispose() throw new NotImplementedException(); } } - - [Fact] - public void InitializesWithSequenceNumber() - { - var injectionScope = new TestInjectionScope(42, _instanceProvider); - Assert.Equal(42, injectionScope.SequenceNumber); - } - - [Fact] - public void KeepsInstanceProvider() - { - var injectionScope = new TestInjectionScope(42, _instanceProvider); - Assert.Equal(_instanceProvider, injectionScope.InstanceProvider); - } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs similarity index 82% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index bcdb7810..4923e461 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -13,7 +13,11 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection public class TheSequentializingBackendFxApplicationInvoker { private static readonly ILogger Logger = LogManager.Create(); - + + private readonly int _actionDuration = 100; + private readonly IBackendFxApplicationInvoker _decoratedInvoker; + private readonly IBackendFxApplicationInvoker _invoker; + public TheSequentializingBackendFxApplicationInvoker() { var fakes = new DiTestFakes(); @@ -21,16 +25,12 @@ public TheSequentializingBackendFxApplicationInvoker() _decoratedInvoker = new SequentializingBackendFxApplicationInvoker(_invoker); } - private readonly int _actionDuration = 100; - private readonly IBackendFxApplicationInvoker _invoker; - private readonly IBackendFxApplicationInvoker _decoratedInvoker; - private async Task InvokeSomeActions(int count, IBackendFxApplicationInvoker invoker) { - var tasks = Enumerable - .Range(0, count) - .Select(i => Task.Run(() => invoker.Invoke(DoTheAction, new AnonymousIdentity(), new TenantId(1)))) - .ToArray(); + Task[] tasks = Enumerable + .Range(0, count) + .Select(_ => Task.Run(() => invoker.Invoke(DoTheAction, new AnonymousIdentity(), new TenantId(1)))) + .ToArray(); await Task.WhenAll(tasks); } @@ -53,14 +53,11 @@ public async Task IsReallyNeeded() sw.Start(); await InvokeSomeActions(count, _invoker); long actualDuration = sw.ElapsedMilliseconds; - var expectedDuration = _actionDuration * count; - Assert.True(actualDuration < expectedDuration, + int expectedDuration = _actionDuration * count; + Assert.True( + actualDuration < expectedDuration, $"Actual duration of {actualDuration}ms is greater than maximum expected duration of {expectedDuration}ms"); } - else - { - // fails on CI Pipeline due to CPU count - } } [Fact] @@ -73,4 +70,4 @@ public async Task SequentializesInvocations() Assert.True(sw.ElapsedMilliseconds >= _actionDuration * count); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs index f4c2f8d0..bf3dac12 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs @@ -10,4 +10,4 @@ public void Handle(TestDomainEvent testDomainEvent) throw new NotSupportedException(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs index c9fb4e59..fd0091fe 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs @@ -11,4 +11,4 @@ public TestDomainEvent(int id) public int Id { get; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs similarity index 80% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs index faa65c19..a1ebe640 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs @@ -5,11 +5,11 @@ namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain { public class TestDomainEventHandler : IDomainEventHandler { - public List Events { get; } = new List(); + public List Events { get; } = new(); public void Handle(TestDomainEvent testDomainEvent) { Events.Add(testDomainEvent); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs similarity index 68% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs index f2adb89e..7e93f9d1 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs @@ -6,11 +6,12 @@ namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain [UsedImplicitly] public class TestIntegrationEvent : IntegrationEvent { - public TestIntegrationEvent(int whatever, int tenantId) : base(tenantId) + public TestIntegrationEvent(int whatever) { Whatever = whatever; } - [UsedImplicitly] public int Whatever { get; } + [UsedImplicitly] + public int Whatever { get; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs similarity index 86% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs index 59263995..e45c28ba 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs @@ -13,14 +13,16 @@ public void CallsAllDomainEventHandlers() var handler1 = new TestDomainEventHandler(); var handler2 = new TestDomainEventHandler(); var fakeDomainEventHandlerProvider = A.Fake(); - A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()).Returns(new[] {handler1, handler2}); + A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()) + .Returns(new[] { handler1, handler2 }); IDomainEventAggregator sut = new DomainEventAggregator(fakeDomainEventHandlerProvider); sut.PublishDomainEvent(new TestDomainEvent(4711)); sut.RaiseEvents(); - A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()).MustHaveHappenedOnceExactly(); + A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()) + .MustHaveHappenedOnceExactly(); Assert.Single(handler1.Events); Assert.Equal(4711, handler1.Events[0].Id); @@ -36,11 +38,12 @@ public void DoesNotSwallowExceptionOnDomainEventHandling() { IDomainEventHandler handler = new FailingDomainEventHandler(); var fakeDomainEventHandlerProvider = A.Fake(); - A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()).Returns(new[] {handler}); + A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()) + .Returns(new[] { handler }); IDomainEventAggregator sut = new DomainEventAggregator(fakeDomainEventHandlerProvider); sut.PublishDomainEvent(new TestDomainEvent(444)); Assert.Throws(() => sut.RaiseEvents()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs similarity index 96% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs index 52fa0fa5..ea1422a9 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs @@ -17,8 +17,8 @@ public BlockingMessageHandler(ManualResetEvent manualResetEvent) public void Handle(TestIntegrationEvent eventData) { Assert.True( - _manualResetEvent.WaitOne(Debugger.IsAttached ? int.MaxValue : 1000), + _manualResetEvent.WaitOne(Debugger.IsAttached ? int.MaxValue : 1000), "The BlockingMessageHandler was not reset."); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs index e0cafc60..fcef7068 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs @@ -16,4 +16,4 @@ public void Handle(dynamic eventData) _integrationMessageHandlerImplementation.Handle(eventData); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs index 2cf3567c..785f3160 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs @@ -18,4 +18,4 @@ public void Handle(TestIntegrationEvent eventData) _handler.Handle(eventData); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs similarity index 71% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs index 906488d3..9ce0de9e 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs @@ -10,22 +10,23 @@ namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration public class SerializingMessageBus : MessageBus { public override void Connect() - { - } + { } protected override Task PublishOnMessageBus(IIntegrationEvent integrationEvent) { - var jsonString = JsonConvert.SerializeObject(integrationEvent); - return Task.Run(() => Process(MessageNameProvider.GetMessageName(), new SerializingProcessingContext(jsonString))); + string jsonString = JsonConvert.SerializeObject(integrationEvent); + return Task.Run( + () => Process( + MessageNameProvider.GetMessageName(), + new SerializingProcessingContext(jsonString))); } protected override void Subscribe(string messageName) - { - } + { } protected override void Unsubscribe(string messageName) - { - } + { } + private class SerializingProcessingContext : EventProcessingContext { @@ -34,7 +35,9 @@ private class SerializingProcessingContext : EventProcessingContext public SerializingProcessingContext(string jsonString) { _jsonString = jsonString; - var eventStub = JsonConvert.DeserializeAnonymousType(jsonString, new {tenantId = 0, correlationId = Guid.Empty}); + var eventStub = JsonConvert.DeserializeAnonymousType( + jsonString, + new { tenantId = 0, correlationId = Guid.Empty }); TenantId = new TenantId(eventStub.tenantId); CorrelationId = eventStub.correlationId; } @@ -42,12 +45,13 @@ public SerializingProcessingContext(string jsonString) public override TenantId TenantId { get; } public override dynamic DynamicEvent => JObject.Parse(_jsonString); + public override Guid CorrelationId { get; } public override IIntegrationEvent GetTypedEvent(Type eventType) { - return (IIntegrationEvent) JsonConvert.DeserializeObject(_jsonString, eventType); + return (IIntegrationEvent)JsonConvert.DeserializeObject(_jsonString, eventType); } } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs similarity index 87% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs index 09e356c0..199fe763 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs @@ -5,7 +5,7 @@ namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { public class TestIntegrationEvent : IntegrationEvent { - public TestIntegrationEvent(int intParam, string stringParam) : base(55) + public TestIntegrationEvent(int intParam, string stringParam) { IntParam = intParam; StringParam = stringParam; @@ -15,7 +15,8 @@ public TestIntegrationEvent(int intParam, string stringParam) : base(55) public string StringParam { get; } - public ManualResetEventSlim TypedProcessed { get; } = new ManualResetEventSlim(false); - public ManualResetEventSlim DynamicProcessed { get; } = new ManualResetEventSlim(false); + public ManualResetEventSlim TypedProcessed { get; } = new(false); + + public ManualResetEventSlim DynamicProcessed { get; } = new(false); } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs similarity index 88% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs index f9c023db..bc71901d 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs @@ -21,28 +21,30 @@ public async Task HandlesEventsAsynchronously() await messageBus.Publish(new TestIntegrationEvent(0, string.Empty)); var finishHandleTask = channel.FinishHandlingAllMessagesAsync(); - Assert.Contains(finishHandleTask.Status, new[] {TaskStatus.WaitingForActivation, TaskStatus.Running}); + Assert.Contains(finishHandleTask.Status, new[] { TaskStatus.WaitingForActivation, TaskStatus.Running }); handled.Set(); await finishHandleTask; } - + [Fact] public async Task InvokesAllApplicationHandlers() { var channel = new InMemoryMessageBusChannel(); - + var messageBus = new InMemoryMessageBus(channel); var eventHandled = false; messageBus.Connect(); messageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); - messageBus.Subscribe(new DelegateIntegrationMessageHandler(ev => eventHandled = true)); - + messageBus.Subscribe( + new DelegateIntegrationMessageHandler(_ => eventHandled = true)); + var anotherMessageBus = new InMemoryMessageBus(channel); var anotherEventHandled = false; anotherMessageBus.Connect(); anotherMessageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); - messageBus.Subscribe(new DelegateIntegrationMessageHandler(ev => anotherEventHandled = true)); + messageBus.Subscribe( + new DelegateIntegrationMessageHandler(_ => anotherEventHandled = true)); await messageBus.Publish(new TestIntegrationEvent(0, string.Empty)); await channel.FinishHandlingAllMessagesAsync(); @@ -52,10 +54,10 @@ public async Task InvokesAllApplicationHandlers() eventHandled = false; anotherEventHandled = false; - + await anotherMessageBus.Publish(new TestIntegrationEvent(0, string.Empty)); await channel.FinishHandlingAllMessagesAsync(); - + Assert.True(eventHandled); Assert.True(anotherEventHandled); } @@ -94,4 +96,4 @@ public async Task DoesAwaitAllPendingMessages() Assert.True(allMessagesAreHandled); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs similarity index 70% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs index f2b5c0c4..acf77620 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs @@ -19,7 +19,9 @@ public TheInMemoryMessageBus() Sut.ProvideInvoker(FakeApplication.Invoker); Sut.Connect(); } - + + protected override IMessageBus Sut { get; } = new InMemoryMessageBus(); + [Fact] public async Task HandlesEventsAsynchronously() { @@ -31,10 +33,9 @@ public async Task HandlesEventsAsynchronously() Assert.True(Invoker.IntegrationEvent.TypedProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); Assert.True(sw.ElapsedMilliseconds > 1000); } - - protected override IMessageBus Sut { get; } = new InMemoryMessageBus(); } + [UsedImplicitly] public sealed class TheSerializingMessageBus : TheMessageBus { @@ -46,54 +47,19 @@ public TheSerializingMessageBus() protected override IMessageBus Sut { get; } = new SerializingMessageBus(); } + public abstract class TheMessageBus { - protected IBackendFxApplication FakeApplication { get; } = A.Fake(); - protected TheMessageBus() { A.CallTo(() => FakeApplication.Invoker).Returns(Invoker); - A.CallTo(() => FakeApplication.WaitForBoot(A._, A._)).Returns(true); } - protected TestInvoker Invoker { get; } = new TestInvoker(); - protected abstract IMessageBus Sut { get; } - - - public class TestInvoker : IBackendFxApplicationInvoker - { - public TestIntegrationEvent IntegrationEvent = new TestIntegrationEvent(34, "gaga"); - - public TestInvoker() - { - A.CallTo(() => TypedHandler.Handle(A._)).Invokes((TestIntegrationEvent e) => IntegrationEvent.TypedProcessed.Set()); - A.CallTo(() => DynamicHandler.Handle(new object())).WithAnyArguments().Invokes((object e) => IntegrationEvent.DynamicProcessed.Set()); - - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(TypedMessageHandler)))) - .Returns(new TypedMessageHandler(TypedHandler)); - - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(LongRunningMessageHandler)))) - .Returns(new LongRunningMessageHandler(TypedHandler)); - - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(ThrowingTypedMessageHandler)))) - .Returns(new ThrowingTypedMessageHandler(TypedHandler)); - - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(DynamicMessageHandler)))) - .Returns(new DynamicMessageHandler(DynamicHandler)); - - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(ThrowingDynamicMessageHandler)))) - .Returns(new ThrowingDynamicMessageHandler(DynamicHandler)); - } + protected IBackendFxApplication FakeApplication { get; } = A.Fake(); - public IIntegrationMessageHandler TypedHandler { get; } = A.Fake>(); - public IIntegrationMessageHandler DynamicHandler { get; } = A.Fake(); - public IInstanceProvider FakeInstanceProvider { get; } = A.Fake(); + protected TestInvoker Invoker { get; } = new(); - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) - { - action(FakeInstanceProvider); - } - } + protected abstract IMessageBus Sut { get; } [Fact] public async void CallsDynamicEventHandler() @@ -116,10 +82,12 @@ public async void CallsMixedEventHandlers() Assert.True(Invoker.IntegrationEvent.TypedProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); Assert.True(Invoker.IntegrationEvent.DynamicProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); - A.CallTo(() => Invoker.TypedHandler.Handle(A - .That - .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) - .MustHaveHappenedOnceExactly(); + A.CallTo( + () => Invoker.TypedHandler.Handle( + A + .That + .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) + .MustHaveHappenedOnceExactly(); A.CallTo(() => Invoker.DynamicHandler.Handle(A._)).MustHaveHappenedOnceExactly(); } @@ -130,26 +98,30 @@ public async Task CallsTypedEventHandler() Sut.Subscribe(); await Sut.Publish(Invoker.IntegrationEvent); Assert.True(Invoker.IntegrationEvent.TypedProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); - A.CallTo(() => Invoker.TypedHandler.Handle(A - .That - .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) - .MustHaveHappenedOnceExactly(); + A.CallTo( + () => Invoker.TypedHandler.Handle( + A + .That + .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) + .MustHaveHappenedOnceExactly(); A.CallTo(() => Invoker.DynamicHandler.Handle(A._)).MustNotHaveHappened(); } - + [Fact] public async Task DoesNotCallUnsubscribedTypedEventHandler() { Sut.Subscribe(); Sut.Unsubscribe(); await Sut.Publish(Invoker.IntegrationEvent); - A.CallTo(() => Invoker.TypedHandler.Handle(A - .That - .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) - .MustNotHaveHappened(); + A.CallTo( + () => Invoker.TypedHandler.Handle( + A + .That + .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) + .MustNotHaveHappened(); } - + [Fact] public async void DoesNotCallUnsubscribedDynamicEventHandler() { @@ -158,12 +130,12 @@ public async void DoesNotCallUnsubscribedDynamicEventHandler() await Sut.Publish(Invoker.IntegrationEvent); A.CallTo(() => Invoker.DynamicHandler.Handle(A._)).MustNotHaveHappened(); } - + [Fact] public async void DoesNotCallUnsubscribedDelegateEventHandler() { var handled = new ManualResetEvent(false); - var handler = new DelegateIntegrationMessageHandler(ev => handled.Set()); + var handler = new DelegateIntegrationMessageHandler(_ => handled.Set()); Sut.Subscribe(handler); Sut.Unsubscribe(handler); await Sut.Publish(Invoker.IntegrationEvent); @@ -175,15 +147,67 @@ public void CannCallConnectButItDoesNothing() { Sut.Connect(); } - + [Fact] public async void DelegateIntegrationMessageHandler() { var handled = new ManualResetEvent(false); - var handler = new DelegateIntegrationMessageHandler(ev => handled.Set()); + var handler = new DelegateIntegrationMessageHandler(_ => handled.Set()); Sut.Subscribe(handler); await Sut.Publish(Invoker.IntegrationEvent); Assert.True(handled.WaitOne(Debugger.IsAttached ? int.MaxValue : 10000)); } + + + public class TestInvoker : IBackendFxApplicationInvoker + { + public readonly TestIntegrationEvent IntegrationEvent = new(34, "gaga"); + + public TestInvoker() + { + A.CallTo(() => TypedHandler.Handle(A._)) + .Invokes((TestIntegrationEvent _) => IntegrationEvent.TypedProcessed.Set()); + A.CallTo(() => DynamicHandler.Handle(new object())) + .WithAnyArguments() + .Invokes((object _) => IntegrationEvent.DynamicProcessed.Set()); + + A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(TypedMessageHandler)))) + .Returns(new TypedMessageHandler(TypedHandler)); + + A.CallTo( + () => FakeInstanceProvider.GetInstance( + A.That.IsEqualTo(typeof(LongRunningMessageHandler)))) + .Returns(new LongRunningMessageHandler(TypedHandler)); + + A.CallTo( + () => FakeInstanceProvider.GetInstance( + A.That.IsEqualTo(typeof(ThrowingTypedMessageHandler)))) + .Returns(new ThrowingTypedMessageHandler(TypedHandler)); + + A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(DynamicMessageHandler)))) + .Returns(new DynamicMessageHandler(DynamicHandler)); + + A.CallTo( + () => FakeInstanceProvider.GetInstance( + A.That.IsEqualTo(typeof(ThrowingDynamicMessageHandler)))) + .Returns(new ThrowingDynamicMessageHandler(DynamicHandler)); + } + + public IIntegrationMessageHandler TypedHandler { get; } + = A.Fake>(); + + public IIntegrationMessageHandler DynamicHandler { get; } = A.Fake(); + + public IInstanceProvider FakeInstanceProvider { get; } = A.Fake(); + + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) + { + action(FakeInstanceProvider); + } + } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs similarity index 56% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs index 6ffc8fae..ba757d9b 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs @@ -1,3 +1,4 @@ +using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; @@ -7,19 +8,20 @@ namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { public class TheMessageBusScope { - private readonly IMessageBus _messageBus = A.Fake(); private readonly ICurrentTHolder _currentCorrelationHolder = new CurrentCorrelationHolder(); + private readonly ICurrentTHolder _currentTenantIdHolder = CurrentTenantIdHolder.Create(1); + private readonly IMessageBus _messageBus = A.Fake(); private readonly IMessageBusScope _sut; public TheMessageBusScope() { - _sut = new MessageBusScope(_messageBus, _currentCorrelationHolder); + _sut = new MessageBusScope(_messageBus, _currentCorrelationHolder, _currentTenantIdHolder); } [Fact] public void MaintainsCorrelationIdOnPublish() { - var testIntegrationEvent = new Domain.TestIntegrationEvent(44, 1111); + var testIntegrationEvent = new Domain.TestIntegrationEvent(44); _sut.Publish(testIntegrationEvent); Assert.Equal(_currentCorrelationHolder.Current.Id, testIntegrationEvent.CorrelationId); } @@ -27,27 +29,27 @@ public void MaintainsCorrelationIdOnPublish() [Fact] public void DoesNotPublishOnBusWhenPublishing() { - var testIntegrationEvent = new Domain.TestIntegrationEvent(44, 1111); + var testIntegrationEvent = new Domain.TestIntegrationEvent(44); _sut.Publish(testIntegrationEvent); - A.CallTo(()=>_messageBus.Publish(A._)).MustNotHaveHappened(); + A.CallTo(() => _messageBus.Publish(A._)).MustNotHaveHappened(); } - + [Fact] public void PublishesAllEventsOnRaise() { - var ev1 = new Domain.TestIntegrationEvent(44, 1111); - var ev2 = new Domain.TestIntegrationEvent(45, 1111); - var ev3 = new Domain.TestIntegrationEvent(46, 1111); - var ev4 = new Domain.TestIntegrationEvent(47, 1111); + var ev1 = new Domain.TestIntegrationEvent(44); + var ev2 = new Domain.TestIntegrationEvent(45); + var ev3 = new Domain.TestIntegrationEvent(46); + var ev4 = new Domain.TestIntegrationEvent(47); _sut.Publish(ev1); _sut.Publish(ev2); _sut.Publish(ev3); _sut.Publish(ev4); _sut.RaiseEvents(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev1))).MustHaveHappenedOnceExactly(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev2))).MustHaveHappenedOnceExactly(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev3))).MustHaveHappenedOnceExactly(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev4))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _messageBus.Publish(A.That.IsEqualTo(ev1))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _messageBus.Publish(A.That.IsEqualTo(ev2))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _messageBus.Publish(A.That.IsEqualTo(ev3))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _messageBus.Publish(A.That.IsEqualTo(ev4))).MustHaveHappenedOnceExactly(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs index a5dd498e..d795b147 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs @@ -19,4 +19,4 @@ public void Handle(dynamic eventData) throw new InvalidOperationException(ExceptionMessage); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs index 3bdd71d9..eeea875b 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs @@ -19,4 +19,4 @@ public void Handle(TestIntegrationEvent eventData) throw new InvalidOperationException(ExceptionMessage); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs similarity index 82% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs index e76416b8..24844cf5 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs @@ -6,7 +6,8 @@ public class TypedMessageHandler : IIntegrationMessageHandler _integrationMessageHandlerImplementation; - public TypedMessageHandler(IIntegrationMessageHandler integrationMessageHandlerImplementation) + public TypedMessageHandler( + IIntegrationMessageHandler integrationMessageHandlerImplementation) { _integrationMessageHandlerImplementation = integrationMessageHandlerImplementation; } @@ -17,4 +18,4 @@ public void Handle(TestIntegrationEvent eventData) eventData.TypedProcessed.Set(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs similarity index 51% rename from tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs index 0d0c5c73..ea7043f0 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs @@ -8,44 +8,55 @@ public class TheHiLoIdGenerator { private readonly HiLoIdGenerator _sut = new InMemoryHiLoIdGenerator(1, 100); - private class IdConsument - { - public int[] Ids { get; private set; } - - public void GetIds(int count, IIdGenerator idGenerator) - { - Ids = new int[count]; - for (var i = 0; i < count; i++) Ids[i] = idGenerator.NextId(); - } - } - [Fact] public void AllowsMultipleThreadsToGetIds() { - const int consumentCount = 50; - const int idCountPerConsument = 1000; - var idConsuments = new IdConsument[consumentCount]; + const int consumerCount = 50; + const int idCountPerConsumer = 1000; + var idConsumers = new IdConsumer[consumerCount]; - for (var i = 0; i < consumentCount; i++) idConsuments[i] = new IdConsument(); + for (var i = 0; i < consumerCount; i++) + { + idConsumers[i] = new IdConsumer(); + } - idConsuments.AsParallel().ForAll(idConsument => { idConsument.GetIds(idCountPerConsument, _sut); }); + idConsumers.AsParallel().ForAll(idConsumer => { idConsumer.GetIds(idCountPerConsumer, _sut); }); - var allIds = idConsuments.SelectMany(idConsument => idConsument.Ids).ToArray(); - Assert.Equal(consumentCount * idCountPerConsument, allIds.Length); - Assert.Equal(consumentCount * idCountPerConsument, allIds.Distinct().Count()); - Assert.Equal(consumentCount * idCountPerConsument + 1, _sut.NextId()); + int[] allIds = idConsumers.SelectMany(idConsumer => idConsumer.Ids).ToArray(); + Assert.Equal(consumerCount * idCountPerConsumer, allIds.Length); + Assert.Equal(consumerCount * idCountPerConsumer, allIds.Distinct().Count()); + Assert.Equal(consumerCount * idCountPerConsumer + 1, _sut.NextId()); } [Fact] public void StartsWithInitialValueAndCountsUp() { - for (var i = 1; i < 1000; i++) Assert.Equal(i, _sut.NextId()); + for (var i = 1; i < 1000; i++) + { + Assert.Equal(i, _sut.NextId()); + } + } + + + private class IdConsumer + { + public int[] Ids { get; private set; } + + public void GetIds(int count, IIdGenerator idGenerator) + { + Ids = new int[count]; + for (var i = 0; i < count; i++) + { + Ids[i] = idGenerator.NextId(); + } + } } } + public class InMemoryHiLoIdGenerator : HiLoIdGenerator { - private readonly object _synclock = new object(); + private readonly object _syncLock = new(); private int _nextBlockStart; public InMemoryHiLoIdGenerator(int start, int increment) @@ -58,13 +69,13 @@ public InMemoryHiLoIdGenerator(int start, int increment) protected override int GetNextBlockStart() { - lock (_synclock) + lock (_syncLock) { // this simulates the behavior of a SQL sequence for example - var result = _nextBlockStart; + int result = _nextBlockStart; _nextBlockStart += BlockSize; return result; } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs new file mode 100644 index 00000000..3685c714 --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs @@ -0,0 +1,57 @@ +using Backend.Fx.Patterns.IdGeneration; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.Tests.Patterns.IdGeneration +{ + public class TheSequenceHiLoIdGenerator + { + private readonly ISequence _sequence = A.Fake(); + private readonly SequenceHiLoIdGenerator _sut; + + public TheSequenceHiLoIdGenerator() + { + A.CallTo(() => _sequence.Increment).Returns(10); + _sut = new SequenceHiLoIdGenerator(_sequence); + } + + [Fact] + public void CallsSequenceNextValueOnBlockStart() + { + for (var i = 0; i < 10; i++) + { + _sut.NextId(); + } + + A.CallTo(() => _sequence.GetNextValue()).MustHaveHappenedOnceExactly(); + _sut.NextId(); + A.CallTo(() => _sequence.GetNextValue()).MustHaveHappenedTwiceExactly(); + } + } + + + public class TheSequenceIdGenerator + { + private readonly ISequence _sequence = A.Fake(); + private readonly SequenceIdGenerator _sut; + + public TheSequenceIdGenerator() + { + A.CallTo(() => _sequence.Increment).Returns(1); + _sut = new SequenceIdGenerator(_sequence); + } + + [Fact] + public void CallsSequenceNextValueOnBlockStart() + { + for (var i = 0; i < 10; i++) + { + _sut.NextId(); + } + + A.CallTo(() => _sequence.GetNextValue()).MustHaveHappenedANumberOfTimesMatching(i => i == 10); + _sut.NextId(); + A.CallTo(() => _sequence.GetNextValue()).MustHaveHappenedANumberOfTimesMatching(i => i == 11); + } + } +} diff --git a/src/abstractions/Backend.Fx.Tests/Patterns/MultiTenancy/TheTenantService.cs b/src/abstractions/Backend.Fx.Tests/Patterns/MultiTenancy/TheTenantService.cs new file mode 100644 index 00000000..79870ceb --- /dev/null +++ b/src/abstractions/Backend.Fx.Tests/Patterns/MultiTenancy/TheTenantService.cs @@ -0,0 +1,99 @@ +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Exceptions; +using Backend.Fx.Patterns.EventAggregation.Integration; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.Tests.Patterns.MultiTenancy +{ + public class TheTenantService + { + private readonly IMessageBus _messageBus = A.Fake(); + private readonly ITenantService _sut; + private readonly InMemoryTenantRepository _tenantRepository = new(); + + public TheTenantService() + { + _sut = new TenantService(_messageBus, _tenantRepository); + } + + [Fact] + public void CanGetTenants() + { + _sut.CreateTenant("t1", "d1", false); + var inactive = _sut.CreateTenant("t2", "d2", false); + _sut.CreateTenant("t3", "d3", false); + _sut.CreateTenant("t4", "d4", true); + + _sut.DeactivateTenant(inactive); + + + Tenant[] tenants = _sut.GetTenants(); + Assert.Equal(4, tenants.Length); + Assert.Contains(tenants, t => t.Name == "t1"); + Assert.Contains(tenants, t => t.Name == "t2"); + Assert.Contains(tenants, t => t.Name == "t3"); + Assert.Contains(tenants, t => t.Name == "t4"); + + tenants = _sut.GetActiveTenants(); + Assert.Equal(3, tenants.Length); + Assert.Contains(tenants, t => t.Name == "t1"); + Assert.Contains(tenants, t => t.Name == "t3"); + Assert.Contains(tenants, t => t.Name == "t4"); + + tenants = _sut.GetActiveProductionTenants(); + Assert.Equal(2, tenants.Length); + Assert.Contains(tenants, t => t.Name == "t1"); + Assert.Contains(tenants, t => t.Name == "t3"); + + tenants = _sut.GetActiveDemonstrationTenants(); + Assert.Single(tenants); + Assert.Contains(tenants, t => t.Name == "t4"); + } + + [Fact] + public void CanGetTenant() + { + var tenantId = _sut.CreateTenant("t1", "d1", false); + var tenant = _sut.GetTenant(tenantId); + Assert.Equal(tenantId.Value, tenant.Id); + + _sut.DeactivateTenant(tenantId); + tenant = _sut.GetTenant(tenantId); + Assert.Equal(tenantId.Value, tenant.Id); + } + + [Fact] + public void CanDeactivateAndActivate() + { + var tenantId = _sut.CreateTenant("t1", "d1", false); + Fake.ClearRecordedCalls(_messageBus); + + _sut.DeactivateTenant(tenantId); + A.CallTo(() => _messageBus.Publish(A._)).MustHaveHappenedOnceExactly(); + Assert.Equal(TenantState.Inactive, _tenantRepository.GetTenant(tenantId).State); + + _sut.ActivateTenant(tenantId); + A.CallTo(() => _messageBus.Publish(A._)).MustHaveHappenedOnceExactly(); + Assert.Equal(TenantState.Active, _tenantRepository.GetTenant(tenantId).State); + } + + [Fact] + public void CannotDeleteActiveTenant() + { + var tenantId = _sut.CreateTenant("t1", "d1", false); + Assert.Throws(() => _sut.DeleteTenant(tenantId)); + } + + [Fact] + public void CanDelete() + { + var tenantId = _sut.CreateTenant("t1", "d1", false); + + _sut.DeactivateTenant(tenantId); + _sut.DeleteTenant(tenantId); + + Assert.Empty(_sut.GetTenants()); + } + } +} diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs similarity index 96% rename from tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs index 7ea4fb88..89e2f338 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs @@ -9,17 +9,17 @@ namespace Backend.Fx.Tests.Patterns.Transactions { public class TheDbTransactionOperationDecorator { + private readonly IDbConnection _dbConnection = A.Fake(); + private readonly IDbTransaction _dbTransaction = A.Fake(); + private readonly IOperation _operation = new Operation(); + private readonly DbTransactionOperationDecorator _sut; + public TheDbTransactionOperationDecorator() { _sut = new DbTransactionOperationDecorator(_dbConnection, _operation); A.CallTo(() => _dbConnection.BeginTransaction(A._)).Returns(_dbTransaction); } - private readonly IDbConnection _dbConnection = A.Fake(); - private readonly IDbTransaction _dbTransaction = A.Fake(); - private readonly IOperation _operation = new Operation(); - private readonly DbTransactionOperationDecorator _sut; - [Theory] [InlineData(IsolationLevel.ReadCommitted)] [InlineData(IsolationLevel.ReadUncommitted)] @@ -29,7 +29,8 @@ public void BeginsTransactionInSpecificIsolationLevel(IsolationLevel isolationLe _sut.SetIsolationLevel(isolationLevel); A.CallTo(() => _dbConnection.State).Returns(ConnectionState.Open); _sut.Begin(); - A.CallTo(() => _dbConnection.BeginTransaction(A.That.IsEqualTo(isolationLevel))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _dbConnection.BeginTransaction(A.That.IsEqualTo(isolationLevel))) + .MustHaveHappenedOnceExactly(); Assert.Equal(_sut.CurrentTransaction, _dbTransaction); } @@ -38,7 +39,8 @@ public void BeginsTransactionInUnspecifiedIsolationLevel() { A.CallTo(() => _dbConnection.State).Returns(ConnectionState.Open); _sut.Begin(); - A.CallTo(() => _dbConnection.BeginTransaction(A.That.IsEqualTo(IsolationLevel.Unspecified))).MustHaveHappenedOnceExactly(); + A.CallTo(() => _dbConnection.BeginTransaction(A.That.IsEqualTo(IsolationLevel.Unspecified))) + .MustHaveHappenedOnceExactly(); Assert.Equal(_sut.CurrentTransaction, _dbTransaction); } @@ -86,7 +88,7 @@ public void DoesNotAllowToChangeIsolationLevenWhenBegun() } [Fact] - public void DoesNotCommitbutRollbackOnCancel() + public void DoesNotCommitButRollsBackOnCancel() { A.CallTo(() => _dbConnection.State).Returns(ConnectionState.Open); _sut.Begin(); @@ -111,7 +113,7 @@ public void DoesNotMaintainConnectionStateOnCompleteWhenProvidingOpenConnection( public void DoesNotMaintainConnectionStateOnCancelWhenProvidingOpenConnection() { A.CallTo(() => _dbConnection.State).Returns(ConnectionState.Open); - + _sut.Begin(); A.CallTo(() => _dbConnection.Open()).MustNotHaveHappened(); _sut.Cancel(); @@ -143,7 +145,6 @@ public void MaintainsConnectionStateOnCompleteWhenProvidingClosedConnection() A.CallTo(() => _dbConnection.Open()).MustHaveHappenedOnceExactly(); _sut.Complete(); A.CallTo(() => _dbConnection.Close()).MustHaveHappenedOnceExactly(); - } [Fact] @@ -199,4 +200,4 @@ public void DoesNotAllowCompleteWhenRolledBack() Assert.Throws(() => _sut.Complete()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs b/src/abstractions/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs similarity index 99% rename from tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs rename to src/abstractions/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs index c2da7500..aa6f3a8e 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs +++ b/src/abstractions/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs @@ -15,6 +15,7 @@ public TheReadonlyDecorator() _operation = A.Fake(); _sut = new ReadonlyDbTransactionOperationDecorator(_operation); } + [Fact] public void DelegatesOtherCalls() { @@ -34,4 +35,4 @@ public void CancelsOperationOnComplete() A.CallTo(() => _operation.Cancel()).MustHaveHappenedOnceExactly(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/RandomData/TheGenerators.cs b/src/abstractions/Backend.Fx.Tests/RandomData/TheGenerators.cs similarity index 87% rename from tests/Backend.Fx.Tests/RandomData/TheGenerators.cs rename to src/abstractions/Backend.Fx.Tests/RandomData/TheGenerators.cs index 91990709..cba5526a 100644 --- a/tests/Backend.Fx.Tests/RandomData/TheGenerators.cs +++ b/src/abstractions/Backend.Fx.Tests/RandomData/TheGenerators.cs @@ -4,26 +4,38 @@ namespace Backend.Fx.Tests.RandomData { - public class TheLandLineGenerator : TheGenerator{} - public class TheMobileLineGenerator : TheGenerator{} - public class TheTestAddressGenerator : TheGenerator{} - public class TheTestPersonGenerator : TheGenerator{} + public class TheLandLineGenerator : TheGenerator + { } + + + public class TheMobileLineGenerator : TheGenerator + { } + + + public class TheTestAddressGenerator : TheGenerator + { } + + + public class TheTestPersonGenerator : TheGenerator + { } + public class TheLoremIpsumGenerator : TheGenerator { [Fact] public void GeneratesAsExpected() { - var sentence = LoremIpsumGenerator.Generate(10, 10, true); + string sentence = LoremIpsumGenerator.Generate(10, 10, true); Assert.Equal(10, sentence.Split(" ").Length); Assert.True(sentence.EndsWith('.')); - + sentence = LoremIpsumGenerator.Generate(10, 10, false); Assert.Equal(10, sentence.Split(" ").Length); Assert.False(sentence.EndsWith('.')); } } - + + public abstract class TheGenerator where TGen : Generator, new() { private readonly TGen _sut; @@ -40,7 +52,7 @@ public void CanGenerateMany() Assert.Equal(100, generated.Length); Assert.Equal(100, generated.Distinct().Count()); } - + [Fact] public void GeneratesNotEqual() { @@ -48,4 +60,4 @@ public void GeneratesNotEqual() Assert.Equal(100, generated.Distinct().Count()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.Tests/TestConfig.cs b/src/abstractions/Backend.Fx.Tests/TestConfig.cs similarity index 67% rename from tests/Backend.Fx.Tests/TestConfig.cs rename to src/abstractions/Backend.Fx.Tests/TestConfig.cs index a905d716..b0e112ee 100644 --- a/tests/Backend.Fx.Tests/TestConfig.cs +++ b/src/abstractions/Backend.Fx.Tests/TestConfig.cs @@ -3,7 +3,10 @@ using MarcWittke.Xunit.AssemblyFixture; using Xunit; -[assembly: TestFramework("MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", "MarcWittke.Xunit.AssemblyFixture")] +[assembly: + TestFramework( + "MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", + "MarcWittke.Xunit.AssemblyFixture")] [assembly: AssemblyFixture(typeof(TestLoggingFixture))] namespace Backend.Fx.Tests @@ -11,7 +14,6 @@ namespace Backend.Fx.Tests public class TestLoggingFixture : LoggingFixture { public TestLoggingFixture() : base("Not.Important.Since.Backend.Fx.Is.Logging.By.Default") - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index 3f24de27..0c6a6a2f 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -1,43 +1,44 @@  - - netstandard1.3 - true - snupkg - false - false - false - false - - - - Marc Wittke - anic GmbH - All rights reserved. Distributed under the terms of the MIT License. - Abstractions of Backend.Fx and generic base implementations. - False - MIT - https://github.com/marcwittke/Backend.Fx - Backend.Fx - Git - https://github.com/marcwittke/Backend.Fx.git - - - - - - - + + netstandard1.3 + true + snupkg + false + false + false + false + - - - - - - - - - - + + Marc Wittke + anic GmbH + All rights reserved. Distributed under the terms of the MIT License. + Abstractions of Backend.Fx and generic base implementations. + False + MIT + https://github.com/marcwittke/Backend.Fx + Backend.Fx + Git + https://github.com/marcwittke/Backend.Fx.git + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs b/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs index e22101f1..1cbff124 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs @@ -1,19 +1,17 @@ namespace Backend.Fx.BuildingBlocks { /// - /// A collection of objects that are bound together by a root entity - /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks + /// A collection of objects that are bound together by a root entity + /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks /// public abstract class AggregateRoot : Entity { protected AggregateRoot() - { - } + { } protected AggregateRoot(int id) : base(id) - { - } + { } public int TenantId { get; set; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs index 5ddc4d22..f7b1f3fa 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs @@ -12,8 +12,7 @@ namespace Backend.Fx.BuildingBlocks public abstract class Entity : Identified { protected Entity() - { - } + { } protected Entity(int id) { @@ -22,11 +21,13 @@ protected Entity(int id) public DateTime CreatedOn { get; protected set; } - [StringLength(100)] public string CreatedBy { get; protected set; } + [StringLength(100)] + public string CreatedBy { get; protected set; } public DateTime? ChangedOn { get; protected set; } - [StringLength(100)] public string ChangedBy { get; protected set; } + [StringLength(100)] + public string ChangedBy { get; protected set; } public void SetCreatedProperties([NotNull] string createdBy, DateTime createdOn) { @@ -60,4 +61,4 @@ public void SetModifiedProperties([NotNull] string changedBy, DateTime changedOn ChangedOn = changedOn; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs index 832d8777..d0169f7f 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs @@ -4,6 +4,5 @@ /// A marker interface to identify application services to be auto registered in the container on boot /// public interface IApplicationService - { - } -} \ No newline at end of file + { } +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs index 9365d5c0..98473bce 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs @@ -6,17 +6,18 @@ namespace Backend.Fx.BuildingBlocks { /// - /// Encapsulates methods for retrieving domain objects + /// Encapsulates methods for retrieving domain objects /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks /// /// public interface IAsyncRepository where TAggregateRoot : AggregateRoot { + IQueryable AggregateQueryable { get; } + Task SingleAsync(int id, CancellationToken cancellationToken = default); Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default); Task GetAllAsync(CancellationToken cancellationToken = default); Task AnyAsync(CancellationToken cancellationToken = default); Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default); - IQueryable AggregateQueryable { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs index ddcc120d..9a054a57 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs @@ -4,6 +4,5 @@ /// A marker interface to domain application services to be auto registered in the container on boot /// public interface IDomainService - { - } -} \ No newline at end of file + { } +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs index 19ba3187..244f2cbd 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs @@ -4,12 +4,14 @@ namespace Backend.Fx.BuildingBlocks { /// - /// Encapsulates methods for retrieving domain objects + /// Encapsulates methods for retrieving domain objects /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks /// /// public interface IRepository where TAggregateRoot : AggregateRoot { + IQueryable AggregateQueryable { get; } + TAggregateRoot Single(int id); TAggregateRoot SingleOrDefault(int id); TAggregateRoot[] GetAll(); @@ -18,6 +20,5 @@ public interface IRepository where TAggregateRoot : AggregateRoo void AddRange(TAggregateRoot[] aggregateRoots); bool Any(); TAggregateRoot[] Resolve(IEnumerable ids); - IQueryable AggregateQueryable { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs index 0121addb..310594d9 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs @@ -7,8 +7,8 @@ namespace Backend.Fx.BuildingBlocks { public interface IView : IQueryable - { - } + { } + public abstract class View : IView { @@ -26,7 +26,7 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable) _viewImplementation).GetEnumerator(); + return ((IEnumerable)_viewImplementation).GetEnumerator(); } public Type ElementType => _viewImplementation.ElementType; @@ -35,4 +35,4 @@ IEnumerator IEnumerable.GetEnumerator() public IQueryProvider Provider => _viewImplementation.Provider; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs index f37a3bde..8f164558 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs @@ -8,9 +8,11 @@ namespace Backend.Fx.BuildingBlocks [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] public abstract class Identified : IEquatable { - [Key] public int Id { get; set; } + [Key] + public int Id { get; set; } - [UsedImplicitly] public string DebuggerDisplay => $"{GetType().Name}[{Id}]"; + [UsedImplicitly] + public string DebuggerDisplay => $"{GetType().Name}[{Id}]"; public bool Equals(Identified other) { @@ -56,4 +58,4 @@ public override int GetHashCode() return !(x == y); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs index 82783e43..4413dd2c 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs @@ -17,9 +17,12 @@ public abstract class Repository : IRepository w private readonly IAggregateAuthorization _aggregateAuthorization; private readonly ICurrentTHolder _tenantIdHolder; - protected Repository(ICurrentTHolder tenantIdHolder, IAggregateAuthorization aggregateAuthorization) + protected Repository( + ICurrentTHolder tenantIdHolder, + IAggregateAuthorization aggregateAuthorization) { - Logger.Trace($"Instantiating a new Repository<{AggregateTypeName}> for tenant [{(tenantIdHolder.Current.HasValue ? tenantIdHolder.Current.Value.ToString() : "null")}]"); + Logger.Trace( + $"Instantiating a new Repository<{AggregateTypeName}> for tenant [{(tenantIdHolder.Current.HasValue ? tenantIdHolder.Current.Value.ToString() : "null")}]"); _tenantIdHolder = tenantIdHolder; _aggregateAuthorization = aggregateAuthorization; } @@ -34,7 +37,8 @@ public IQueryable AggregateQueryable { if (_tenantIdHolder.Current.HasValue) { - return _aggregateAuthorization.Filter(RawAggregateQueryable + return _aggregateAuthorization.Filter( + RawAggregateQueryable .Where(agg => agg.TenantId == _tenantIdHolder.Current.Value)); } @@ -45,7 +49,7 @@ public IQueryable AggregateQueryable public TAggregateRoot Single(int id) { Logger.Debug($"Getting single {AggregateTypeName}[{id}]"); - TAggregateRoot aggregateRoot = AggregateQueryable.FirstOrDefault(aggr => aggr.Id.Equals(id)); + var aggregateRoot = AggregateQueryable.FirstOrDefault(aggr => aggr.Id.Equals(id)); if (aggregateRoot == null) { throw new NotFoundException(id); @@ -73,9 +77,11 @@ public void Delete([NotNull] TAggregateRoot aggregateRoot) } Logger.Debug($"Deleting {AggregateTypeName}[{aggregateRoot.Id}]"); - if (aggregateRoot.TenantId != _tenantIdHolder.Current.Value || !_aggregateAuthorization.CanDelete(aggregateRoot)) + if (aggregateRoot.TenantId != _tenantIdHolder.Current.Value || + !_aggregateAuthorization.CanDelete(aggregateRoot)) { - throw new ForbiddenException($"You are not allowed to delete {typeof(TAggregateRoot).Name}[{aggregateRoot.Id}]"); + throw new ForbiddenException( + $"You are not allowed to delete {typeof(TAggregateRoot).Name}[{aggregateRoot.Id}]"); } DeletePersistent(aggregateRoot); @@ -96,7 +102,8 @@ public void Add([NotNull] TAggregateRoot aggregateRoot) } else { - throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}"); + throw new ForbiddenException( + $"You are not allowed to create records of type {typeof(TAggregateRoot).Name}"); } } @@ -107,14 +114,16 @@ public void AddRange([NotNull] TAggregateRoot[] aggregateRoots) throw new ArgumentNullException(nameof(aggregateRoots)); } - aggregateRoots.ForAll(agg => - { - if (!_aggregateAuthorization.CanCreate(agg)) + aggregateRoots.ForAll( + agg => { - throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}"); - } - }); - + if (!_aggregateAuthorization.CanCreate(agg)) + { + throw new ForbiddenException( + $"You are not allowed to create records of type {typeof(TAggregateRoot).Name}"); + } + }); + Logger.Debug($"Adding {aggregateRoots.Length} items of type {AggregateTypeName}"); aggregateRoots.ForAll(agg => agg.TenantId = _tenantIdHolder.Current.Value); @@ -138,8 +147,10 @@ public TAggregateRoot[] Resolve(IEnumerable ids) TAggregateRoot[] resolved = AggregateQueryable.Where(agg => idsToResolve.Contains(agg.Id)).ToArray(); if (resolved.Length != idsToResolve.Length) { - throw new ArgumentException($"The following {AggregateTypeName} ids could not be resolved: {string.Join(", ", idsToResolve.Except(resolved.Select(agg => agg.Id)))}"); + throw new ArgumentException( + $"The following {AggregateTypeName} ids could not be resolved: {string.Join(", ", idsToResolve.Except(resolved.Select(agg => agg.Id)))}"); } + return resolved; } @@ -149,4 +160,4 @@ public TAggregateRoot[] Resolve(IEnumerable ids) protected abstract void DeletePersistent(TAggregateRoot aggregateRoot); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs b/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs index f4d5a5f3..2d0fa714 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs @@ -18,10 +18,22 @@ public abstract class ValueObject public override bool Equals(object obj) { - if (ReferenceEquals(this, obj)) return true; - if (ReferenceEquals(null, obj)) return false; - if (GetType() != obj.GetType()) return false; - return GetEqualityComponents().SequenceEqual(((ValueObject) obj).GetEqualityComponents()); + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (GetType() != obj.GetType()) + { + return false; + } + + return GetEqualityComponents().SequenceEqual(((ValueObject)obj).GetEqualityComponents()); } public override int GetHashCode() @@ -39,6 +51,7 @@ public override int GetHashCode() } } + public abstract class ComparableValueObject : ValueObject, IComparable { public int CompareTo(object obj) @@ -74,38 +87,41 @@ protected int CompareTo(ComparableValueObject other) { return 1; } - - using (var thisComponents = GetComparableComponents().GetEnumerator()) - using (var otherComponents = other.GetComparableComponents().GetEnumerator()) + + using (IEnumerator thisComponents = GetComparableComponents().GetEnumerator()) { - while (true) + using (IEnumerator otherComponents = other.GetComparableComponents().GetEnumerator()) { - var x = thisComponents.MoveNext(); - var y = otherComponents.MoveNext(); - if (x != y) + while (true) { - throw new InvalidOperationException(); - } + bool x = thisComponents.MoveNext(); + bool y = otherComponents.MoveNext(); + if (x != y) + { + throw new InvalidOperationException(); + } - if (x) - { - var c = thisComponents.Current?.CompareTo(otherComponents.Current) ?? 0; - if (c != 0) + if (x) { - return c; + int c = thisComponents.Current?.CompareTo(otherComponents.Current) ?? 0; + if (c != 0) + { + return c; + } + } + else + { + break; } } - else - { - break; - } - } - return 0; + return 0; + } } } } + public abstract class ComparableValueObject : ComparableValueObject, IComparable where T : ComparableValueObject { @@ -114,4 +130,4 @@ public int CompareTo(T other) return base.CompareTo(other); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs index 4fc9868d..436f916c 100644 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs +++ b/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs @@ -5,8 +5,8 @@ namespace Backend.Fx.ConfigurationSettings { public interface ISettingSerializer - { - } + { } + public interface ISettingSerializer : ISettingSerializer { @@ -14,6 +14,7 @@ public interface ISettingSerializer : ISettingSerializer T Deserialize(string value); } + [UsedImplicitly] public class StringSerializer : ISettingSerializer { @@ -28,6 +29,7 @@ public string Deserialize(string value) } } + [UsedImplicitly] public class IntegerSerializer : ISettingSerializer { @@ -38,10 +40,11 @@ public string Serialize(int? setting) public int? Deserialize(string value) { - return string.IsNullOrWhiteSpace(value) ? (int?) null : int.Parse(value, CultureInfo.InvariantCulture); + return string.IsNullOrWhiteSpace(value) ? (int?)null : int.Parse(value, CultureInfo.InvariantCulture); } } + [UsedImplicitly] public class DoubleSerializer : ISettingSerializer { @@ -52,10 +55,11 @@ public string Serialize(double? setting) public double? Deserialize(string value) { - return string.IsNullOrWhiteSpace(value) ? (double?) null : double.Parse(value, CultureInfo.InvariantCulture); + return string.IsNullOrWhiteSpace(value) ? (double?)null : double.Parse(value, CultureInfo.InvariantCulture); } } + [UsedImplicitly] public class BooleanSerializer : ISettingSerializer { @@ -66,10 +70,11 @@ public string Serialize(bool? setting) public bool? Deserialize(string value) { - return string.IsNullOrWhiteSpace(value) ? (bool?) null : bool.Parse(value); + return string.IsNullOrWhiteSpace(value) ? (bool?)null : bool.Parse(value); } } + [UsedImplicitly] public class DateTimeSerializer : ISettingSerializer { @@ -80,7 +85,9 @@ public string Serialize(DateTime? setting) public DateTime? Deserialize(string value) { - return string.IsNullOrWhiteSpace(value) ? (DateTime?) null : DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + return string.IsNullOrWhiteSpace(value) + ? (DateTime?)null + : DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs index c1e55a92..ec6a9250 100644 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs +++ b/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs @@ -7,8 +7,7 @@ public class Setting : AggregateRoot { [UsedImplicitly] private Setting() - { - } + { } public Setting(int id, string key) : base(id) { @@ -16,6 +15,7 @@ public Setting(int id, string key) : base(id) } public string Key { get; [UsedImplicitly] private set; } + public string SerializedValue { get; private set; } public T GetValue(ISettingSerializer serializer) @@ -28,4 +28,4 @@ public void SetValue(ISettingSerializer serializer, T value) SerializedValue = serializer.Serialize(value); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs index a270989c..72c138b2 100644 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs +++ b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs @@ -11,35 +11,39 @@ public interface ISettingSerializerFactory ISettingSerializer GetSerializer(); } + public class SettingSerializerFactory : ISettingSerializerFactory { - protected Dictionary Serializers { get; } - public SettingSerializerFactory() { Serializers = typeof(ISettingSerializer) - .GetTypeInfo() - .Assembly - .ExportedTypes - .Select(t => t.GetTypeInfo()) - .Where(t => !t.IsAbstract && t.IsClass && typeof(ISettingSerializer).GetTypeInfo().IsAssignableFrom(t)) - .ToDictionary( - t => t.ImplementedInterfaces - .Single(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(ISettingSerializer<>)) - .GenericTypeArguments.Single(), - t => (ISettingSerializer) Activator.CreateInstance(t.AsType())); + .GetTypeInfo() + .Assembly + .ExportedTypes + .Select(t => t.GetTypeInfo()) + .Where(t => !t.IsAbstract && t.IsClass && typeof(ISettingSerializer).GetTypeInfo().IsAssignableFrom(t)) + .ToDictionary( + t => t.ImplementedInterfaces + .Single( + i => i.GetTypeInfo().IsGenericType && + i.GetGenericTypeDefinition() == typeof(ISettingSerializer<>)) + .GenericTypeArguments.Single(), + t => (ISettingSerializer)Activator.CreateInstance(t.AsType())); } + protected Dictionary Serializers { get; } + [NotNull] public ISettingSerializer GetSerializer() { if (Serializers.ContainsKey(typeof(T))) { - return (ISettingSerializer) Serializers[typeof(T)]; + return (ISettingSerializer)Serializers[typeof(T)]; } - throw new ArgumentOutOfRangeException(nameof(T), - $"No Serializer for Setting Type {typeof(T).Name} available"); + throw new ArgumentOutOfRangeException( + nameof(T), + $"No Serializer for Setting Type {typeof(T).Name} available"); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs index 26476479..8e6f8924 100644 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs +++ b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs @@ -11,7 +11,11 @@ public abstract class SettingsService private readonly IRepository _settingRepository; private readonly ISettingSerializerFactory _settingSerializerFactory; - protected SettingsService(string category, IEntityIdGenerator idGenerator, IRepository settingRepository, ISettingSerializerFactory settingSerializerFactory) + protected SettingsService( + string category, + IEntityIdGenerator idGenerator, + IRepository settingRepository, + ISettingSerializerFactory settingSerializerFactory) { _category = category; _idGenerator = idGenerator; @@ -21,20 +25,20 @@ protected SettingsService(string category, IEntityIdGenerator idGenerator, IRepo protected T ReadSetting(string key) { - var categoryKey = _category + "." + key; + string categoryKey = _category + "." + key; var setting = _settingRepository.AggregateQueryable.SingleOrDefault(s => s.Key == categoryKey); if (setting == null) { - return default(T); + return default; } - var serializer = _settingSerializerFactory.GetSerializer(); + ISettingSerializer serializer = _settingSerializerFactory.GetSerializer(); return setting.GetValue(serializer); } protected void WriteSetting(string key, T value) { - var categoryKey = _category + "." + key; + string categoryKey = _category + "." + key; var setting = _settingRepository.AggregateQueryable.SingleOrDefault(s => s.Key == categoryKey); if (setting == null) { @@ -42,8 +46,8 @@ protected void WriteSetting(string key, T value) _settingRepository.Add(setting); } - var serializer = _settingSerializerFactory.GetSerializer(); + ISettingSerializer serializer = _settingSerializerFactory.GetSerializer(); setting.SetValue(serializer, value); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs b/src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs index 3ef29a26..3ecadbde 100644 --- a/src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs +++ b/src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs @@ -10,4 +10,4 @@ public class AnonymousIdentity : IIdentity public bool IsAuthenticated => false; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs b/src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs index 9799769d..dd870d02 100644 --- a/src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs +++ b/src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs @@ -10,7 +10,7 @@ public CurrentIdentityHolder() private CurrentIdentityHolder(IIdentity initial) : base(initial) { } - + public override IIdentity ProvideInstance() { return new AnonymousIdentity(); @@ -23,7 +23,9 @@ protected override string Describe(IIdentity instance) return ""; } - string auth = instance.IsAuthenticated ? $"authenticated via {instance.AuthenticationType}" : "not authenticated"; + string auth = instance.IsAuthenticated + ? $"authenticated via {instance.AuthenticationType}" + : "not authenticated"; return $"Identity: {instance.Name}, {auth}"; } @@ -31,10 +33,10 @@ public static ICurrentTHolder CreateSystem() { return Create(new SystemIdentity()); } - + public static ICurrentTHolder Create(IIdentity identity) { return new CurrentIdentityHolder(identity); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs b/src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs index cb388295..dc06045c 100644 --- a/src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs +++ b/src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs @@ -10,4 +10,4 @@ public class SystemIdentity : IIdentity public bool IsAuthenticated => true; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs index 9af417ae..c66579af 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs @@ -6,7 +6,7 @@ namespace Backend.Fx.Environment.DateAndTime public class AdjustableClock : IClock { private static readonly ILogger Logger = LogManager.Create(); - + private readonly IClock _clockImplementation; private DateTime? _overriddenUtcNow; @@ -30,5 +30,10 @@ public DateTime Advance(TimeSpan timespan) _overriddenUtcNow = _overriddenUtcNow.Value.Add(timespan); return _overriddenUtcNow.Value; } + + public void ResetToOriginalTime() + { + _overriddenUtcNow = null; + } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs index 2bcf206f..c07706a2 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs @@ -9,14 +9,14 @@ namespace Backend.Fx.Environment.DateAndTime public class FrozenClock : IClock { private static readonly ILogger Logger = LogManager.Create(); - + // ReSharper disable once UnusedParameter.Local public FrozenClock(IClock clock) { - UtcNow = DateTime.UtcNow; + UtcNow = clock.UtcNow; Logger.Trace($"Freezing clock at {UtcNow}"); } public DateTime UtcNow { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs index dcd4a886..8681357b 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs @@ -4,10 +4,11 @@ namespace Backend.Fx.Environment.DateAndTime { /// /// Wraps access to DateTime.UtcNow. By means of this interface the current time can be mocked. - /// the database should only store universal date and time values, that could be translated into user's time by applying a UtcOffset + /// the database should only store universal date and time values, that could be translated into user's time by applying a + /// UtcOffset /// public interface IClock { DateTime UtcNow { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs index 5ca43a75..917c0ddc 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs @@ -9,4 +9,4 @@ public class WallClock : IClock { public DateTime UtcNow => DateTime.UtcNow; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs index 4d89d2c2..ed50e9c8 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs @@ -9,10 +9,12 @@ namespace Backend.Fx.Environment.MultiTenancy public class AllTenantBackendFxApplicationInvoker { private static readonly ILogger Logger = LogManager.Create(); - private readonly ITenantIdProvider _tenantIdProvider; private readonly IBackendFxApplicationInvoker _invoker; + private readonly ITenantIdProvider _tenantIdProvider; - public AllTenantBackendFxApplicationInvoker(ITenantIdProvider tenantIdProvider, IBackendFxApplicationInvoker invoker) + public AllTenantBackendFxApplicationInvoker( + ITenantIdProvider tenantIdProvider, + IBackendFxApplicationInvoker invoker) { _tenantIdProvider = tenantIdProvider; _invoker = invoker; @@ -21,12 +23,14 @@ public AllTenantBackendFxApplicationInvoker(ITenantIdProvider tenantIdProvider, public void Invoke(Action action) { var correlationId = Guid.NewGuid(); - TenantId[] tenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds().Concat(_tenantIdProvider.GetActiveProductionTenantIds()).ToArray(); + TenantId[] tenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds() + .Concat(_tenantIdProvider.GetActiveProductionTenantIds()) + .ToArray(); Logger.Debug($"Action will be called in tenants: {string.Join(",", tenantIds.Select(t => t.ToString()))}"); - foreach (TenantId tenantId in tenantIds) + foreach (var tenantId in tenantIds) { _invoker.Invoke(action, new SystemIdentity(), tenantId, correlationId); } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs index c5c4f1c2..e79e4ba1 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs @@ -9,7 +9,7 @@ public CurrentTenantIdHolder() private CurrentTenantIdHolder(TenantId initial) : base(initial) { } - + public static CurrentTenantIdHolder Create(int tenantId) { var instance = new CurrentTenantIdHolder((TenantId)tenantId); @@ -42,4 +42,4 @@ protected override string Describe(TenantId instance) return "TenantId: null"; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs index 44873e50..2065da5e 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs @@ -1,9 +1,11 @@ namespace Backend.Fx.Environment.MultiTenancy { /// - /// By means of this instance, the IBackendFxApplication gains insight about all active tenants. This is required, when for example a job + /// By means of this instance, the IBackendFxApplication gains insight about all active tenants. This is required, when for + /// example a job /// should be executed for all tenants or data should be generated for all tenants during startup. - /// The can provide such implementation, but this can only be done in process. When the tenant service is + /// The can provide such implementation, but this can only be done in process. When the + /// tenant service is /// running in another process, the implementation must be done using a suitable remoting technology. /// public interface ITenantIdProvider @@ -12,4 +14,4 @@ public interface ITenantIdProvider TenantId[] GetActiveDemonstrationTenantIds(); TenantId[] GetActiveProductionTenantIds(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs index 2f8292f5..4c850085 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs @@ -10,4 +10,4 @@ public interface ITenantRepository void DeleteTenant(TenantId tenantId); } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryTenantRepository.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/InMemoryTenantRepository.cs similarity index 51% rename from src/implementations/Backend.Fx.InMemoryPersistence/InMemoryTenantRepository.cs rename to src/abstractions/Backend.Fx/Environment/MultiTenancy/InMemoryTenantRepository.cs index b9f2f2ad..e4f2b481 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryTenantRepository.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/InMemoryTenantRepository.cs @@ -1,13 +1,40 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -using Backend.Fx.Environment.MultiTenancy; +using JetBrains.Annotations; -namespace Backend.Fx.InMemoryPersistence +namespace Backend.Fx.Environment.MultiTenancy { public class InMemoryTenantRepository : ITenantRepository { private readonly Dictionary _store = new Dictionary(); + public InMemoryTenantRepository() + { } + + /// + /// If your tenants are kept as a file that is loaded on application startup, you can initialize this instance + /// using this constructor. + /// + /// + public InMemoryTenantRepository([NotNull] params Tenant[] tenants) + { + if (tenants == null) + { + throw new ArgumentNullException(nameof(tenants)); + } + + _store = tenants.ToDictionary(t => t.Id, t => t); + } + + /// + /// If your tenants are kept as a file that is loaded on application startup, you can initialize this instance + /// using this constructor. + /// + /// + public InMemoryTenantRepository([NotNull] IEnumerable tenants) : this(tenants.ToArray()) + { } + public Tenant[] GetTenants() { return _store.Values.ToArray(); @@ -40,4 +67,4 @@ public void SaveTenant(Tenant tenant) } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/MultiTenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/MultiTenantApplication.cs index 86dd3191..4037c8af 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/MultiTenantApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/MultiTenantApplication.cs @@ -1,4 +1,3 @@ -using System; using System.Threading; using System.Threading.Tasks; using Backend.Fx.Patterns.DependencyInjection; @@ -28,17 +27,11 @@ public void Dispose() public IMessageBus MessageBus => _application.MessageBus; - public bool WaitForBoot(int timeoutMilliSeconds = Int32.MaxValue, CancellationToken cancellationToken = default) - { - return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); public async Task BootAsync(CancellationToken cancellationToken = default) { EnableDataGenerationForNewTenants(); - + await _application.BootAsync(cancellationToken); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs index 775a65e1..1bbe9610 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs @@ -10,18 +10,23 @@ namespace Backend.Fx.Environment.MultiTenancy public class SingleTenantApplication : TenantApplication, IBackendFxApplication { private static readonly ILogger Logger = LogManager.Create(); - private readonly bool _isDemoTenant; - private readonly ITenantService _tenantService; private readonly IBackendFxApplication _application; private readonly ManualResetEventSlim _defaultTenantEnsured = new ManualResetEventSlim(false); - - public SingleTenantApplication(bool isDemoTenant, ITenantService tenantService, IBackendFxApplication application) : base(application) + private readonly bool _isDemoTenant; + private readonly ITenantService _tenantService; + + public SingleTenantApplication( + bool isDemoTenant, + ITenantService tenantService, + IBackendFxApplication application) : base(application) { _isDemoTenant = isDemoTenant; _tenantService = tenantService; _application = application; } + public TenantId TenantId { get; private set; } + public void Dispose() { _application.Dispose(); @@ -35,14 +40,6 @@ public void Dispose() public IMessageBus MessageBus => _application.MessageBus; - public TenantId TenantId { get; private set; } - - public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default) - { - return _defaultTenantEnsured.Wait(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); public async Task BootAsync(CancellationToken cancellationToken = default) { EnableDataGenerationForNewTenants(); @@ -51,9 +48,12 @@ public async Task BootAsync(CancellationToken cancellationToken = default) Logger.Info("Ensuring existence of single tenant"); TenantId = _tenantService.GetActiveTenants().SingleOrDefault()?.GetTenantId() - ?? _tenantService.CreateTenant("Single Tenant", "This application runs in single tenant mode", _isDemoTenant); + ?? _tenantService.CreateTenant( + "Single Tenant", + "This application runs in single tenant mode", + _isDemoTenant); _defaultTenantEnsured.Set(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs index b226562f..79da3ab9 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs @@ -11,12 +11,15 @@ public class Tenant { [UsedImplicitly] private Tenant() - { - } + { } public Tenant([NotNull] string name, string description, bool isDemoTenant, string configuration = null) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + Name = name; Description = description; IsDemoTenant = isDemoTenant; @@ -24,9 +27,11 @@ public Tenant([NotNull] string name, string description, bool isDemoTenant, stri State = TenantState.Active; } - [Key] public int Id { get; set; } + [Key] + public int Id { get; set; } - [Required] public string Name { get; set; } + [Required] + public string Name { get; set; } public string Description { get; set; } @@ -35,7 +40,7 @@ public Tenant([NotNull] string name, string description, bool isDemoTenant, stri public TenantState State { get; set; } /// - /// optional: a generic field to store your arbitrary config data + /// optional: a generic field to store your arbitrary config data /// public string Configuration { get; set; } @@ -45,9 +50,10 @@ public TenantId GetTenantId() } } + public enum TenantState { Active = 2, Inactive = -1 } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs index d4bc387d..048f1c02 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs @@ -2,9 +2,8 @@ { public class TenantActivated : TenantStatusChanged { - public TenantActivated(int tenantId, string name, string description, bool isDemoTenant) - : base(tenantId, name, description, isDemoTenant) - { - } + public TenantActivated(string name, string description, bool isDemoTenant) + : base(name, description, isDemoTenant) + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantApplication.cs index 32ce9ac8..b108d624 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantApplication.cs @@ -13,27 +13,33 @@ public abstract class TenantApplication private readonly IBackendFxApplication _application; private readonly DataGenerationContext _dataGenerationContext; - protected TenantApplication(IBackendFxApplication application) { _application = application; _dataGenerationContext = new DataGenerationContext(_application.CompositionRoot, _application.Invoker); } - + protected void EnableDataGenerationForNewTenants() { - _application.MessageBus.Subscribe(new DelegateIntegrationMessageHandler(tenantCreated => - { - Logger.Info($"Seeding data for recently activated {(tenantCreated.IsDemoTenant ? "demo " : "")}tenant {tenantCreated.TenantId}"); - try - { - _dataGenerationContext.SeedDataForTenant(new TenantId(tenantCreated.TenantId), tenantCreated.IsDemoTenant); - } - catch (Exception ex) - { - Logger.Error(ex, $"Seeding data for recently activated {(tenantCreated.IsDemoTenant ? "demo " : "")}tenant {tenantCreated.TenantId} failed."); - } - })); + _application.MessageBus.Subscribe( + new DelegateIntegrationMessageHandler( + tenantCreated => + { + Logger.Info( + $"Seeding data for recently activated {(tenantCreated.IsDemoTenant ? "demo " : "")}tenant {tenantCreated.TenantId}"); + try + { + _dataGenerationContext.SeedDataForTenant( + new TenantId(tenantCreated.TenantId), + tenantCreated.IsDemoTenant); + } + catch (Exception ex) + { + Logger.Error( + ex, + $"Seeding data for recently activated {(tenantCreated.IsDemoTenant ? "demo " : "")}tenant {tenantCreated.TenantId} failed."); + } + })); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs index eb2399d9..0a8fd6b5 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs @@ -2,9 +2,8 @@ { public class TenantDeactivated : TenantStatusChanged { - public TenantDeactivated(int tenantId, string name, string description, bool isDemoTenant) - : base(tenantId, name, description, isDemoTenant) - { - } + public TenantDeactivated(string name, string description, bool isDemoTenant) + : base(name, description, isDemoTenant) + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs index fbcd5ffd..d56f408b 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs @@ -24,7 +24,8 @@ public int Value { if (_id == null) { - throw new InvalidOperationException("You must not access the Value property when the tenant id is null"); + throw new InvalidOperationException( + "You must not access the Value property when the tenant id is null"); } return _id.Value; @@ -55,8 +56,15 @@ protected override IEnumerable GetEqualityComponents() { yield return _id; } - - public static explicit operator int(TenantId tid) => tid.Value; - public static explicit operator TenantId(int id) => new TenantId(id); + + public static explicit operator int(TenantId tid) + { + return tid.Value; + } + + public static explicit operator TenantId(int id) + { + return new TenantId(id); + } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs index 37c5a1db..56bf8dc2 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs @@ -7,19 +7,12 @@ namespace Backend.Fx.Environment.MultiTenancy { /// - /// Encapsulates the management of tenants - /// Note that this should not use repositories and other building blocks, but access the persistence layer directly + /// Encapsulates the management of tenants. Note that the implementation must not rely on services provided by the + /// application or + /// domain layer, but resembles a separate application with exclusive persistence layer. /// public interface ITenantService { - /// - /// The tenant service can also provide an . Keep in mind that this instance uses a direct - /// database connection. When multiple microservices do not share the same database, this instance cannot be used, but must - /// be implemented by a client to the master tenant service, probably using a remoting technology like RESTful Service, HTTP, - /// gRPC or SOAP web service - /// - ITenantIdProvider TenantIdProvider { get; } - TenantId CreateTenant(string name, string description, bool isDemonstrationTenant, string configuration = null); void ActivateTenant(TenantId tenantId); void DeactivateTenant(TenantId tenantId); @@ -32,64 +25,67 @@ public interface ITenantService Tenant GetTenant(TenantId tenantId); } + public class TenantService : ITenantService { private static readonly ILogger Logger = LogManager.Create(); private readonly IMessageBus _messageBus; private readonly ITenantRepository _tenantRepository; - public ITenantIdProvider TenantIdProvider { get; } - public TenantService(IMessageBus messageBus, ITenantRepository tenantRepository) { _messageBus = messageBus; _tenantRepository = tenantRepository; - TenantIdProvider = new TenantServiceTenantIdProvider(this); } - public TenantId CreateTenant(string name, string description, bool isDemonstrationTenant, string configuration = null) + public TenantId CreateTenant( + string name, + string description, + bool isDemonstrationTenant, + string configuration = null) { Logger.Info($"Creating tenant: {name}"); - + if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); } - if (_tenantRepository.GetTenants().Any(t => t.Name != null && t.Name.ToLowerInvariant() == name.ToLowerInvariant())) + if (_tenantRepository.GetTenants() + .Any(t => t.Name != null && t.Name.ToLowerInvariant() == name.ToLowerInvariant())) { throw new ArgumentException($"There is already a tenant named {name}"); } - var tenant = new Tenant(name, description, isDemonstrationTenant) {Configuration = configuration}; + var tenant = new Tenant(name, description, isDemonstrationTenant) { Configuration = configuration }; _tenantRepository.SaveTenant(tenant); var tenantId = new TenantId(tenant.Id); - _messageBus.Publish(new TenantActivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); + _messageBus.Publish(new TenantActivated(tenant.Name, tenant.Description, tenant.IsDemoTenant)); return tenantId; } public void ActivateTenant(TenantId tenantId) { Logger.Info($"Activating tenant: {tenantId}"); - Tenant tenant = _tenantRepository.GetTenant(tenantId); + var tenant = _tenantRepository.GetTenant(tenantId); tenant.State = TenantState.Active; _tenantRepository.SaveTenant(tenant); - _messageBus.Publish(new TenantActivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); + _messageBus.Publish(new TenantActivated(tenant.Name, tenant.Description, tenant.IsDemoTenant)); } public void DeactivateTenant(TenantId tenantId) { Logger.Info($"Deactivating tenant: {tenantId}"); - Tenant tenant = _tenantRepository.GetTenant(tenantId); + var tenant = _tenantRepository.GetTenant(tenantId); tenant.State = TenantState.Inactive; _tenantRepository.SaveTenant(tenant); - _messageBus.Publish(new TenantDeactivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); + _messageBus.Publish(new TenantDeactivated(tenant.Name, tenant.Description, tenant.IsDemoTenant)); } public void DeleteTenant(TenantId tenantId) { Logger.Info($"Deleting tenant: {tenantId}"); - Tenant tenant = _tenantRepository.GetTenant(tenantId); + var tenant = _tenantRepository.GetTenant(tenantId); if (tenant.State != TenantState.Inactive) { throw new UnprocessableException($"Attempt to delete active tenant[{tenantId.Value}]") @@ -106,70 +102,41 @@ public Tenant GetTenant(TenantId tenantId) public Tenant[] GetTenants() { - var tenants = _tenantRepository.GetTenants(); + Tenant[] tenants = _tenantRepository.GetTenants(); Logger.Trace($"TenantIds: {string.Join(",", tenants.Select(t => t.ToString()))}"); return tenants; } public Tenant[] GetActiveTenants() { - var activeTenants = _tenantRepository - .GetTenants() - .Where(t => t.State == TenantState.Active) - .ToArray(); + Tenant[] activeTenants = _tenantRepository + .GetTenants() + .Where(t => t.State == TenantState.Active) + .ToArray(); Logger.Trace($"Active TenantIds: {string.Join(",", activeTenants.Select(t => t.ToString()))}"); return activeTenants; } public Tenant[] GetActiveDemonstrationTenants() { - var activeDemonstrationTenants = _tenantRepository - .GetTenants() - .Where(t => t.State == TenantState.Active && t.IsDemoTenant) - .ToArray(); - Logger.Trace($"Active Demonstration TenantIds: {string.Join(",", activeDemonstrationTenants.Select(t => t.ToString()))}"); + Tenant[] activeDemonstrationTenants = _tenantRepository + .GetTenants() + .Where(t => t.State == TenantState.Active && t.IsDemoTenant) + .ToArray(); + Logger.Trace( + $"Active Demonstration TenantIds: {string.Join(",", activeDemonstrationTenants.Select(t => t.ToString()))}"); return activeDemonstrationTenants; } public Tenant[] GetActiveProductionTenants() { - var activeProductionTenants = _tenantRepository - .GetTenants() - .Where(t => t.State == TenantState.Active && !t.IsDemoTenant) - .ToArray(); - Logger.Trace($"Active Production TenantIds: {string.Join(",", activeProductionTenants.Select(t => t.ToString()))}"); + Tenant[] activeProductionTenants = _tenantRepository + .GetTenants() + .Where(t => t.State == TenantState.Active && !t.IsDemoTenant) + .ToArray(); + Logger.Trace( + $"Active Production TenantIds: {string.Join(",", activeProductionTenants.Select(t => t.ToString()))}"); return activeProductionTenants; } - - private class TenantServiceTenantIdProvider : ITenantIdProvider - { - private readonly ITenantService _tenantService; - - public TenantServiceTenantIdProvider(ITenantService tenantService) - { - _tenantService = tenantService; - } - - public TenantId[] GetActiveDemonstrationTenantIds() - { - return _tenantService.GetActiveDemonstrationTenants() - .Select(t => new TenantId(t.Id)) - .ToArray(); - } - - public TenantId[] GetActiveProductionTenantIds() - { - return _tenantService.GetActiveProductionTenants() - .Select(t => new TenantId(t.Id)) - .ToArray(); - } - - public TenantId[] GetActiveTenantIds() - { - return _tenantService.GetActiveTenants() - .Select(t => new TenantId(t.Id)) - .ToArray(); - } - } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantServiceTenantIdProvider.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantServiceTenantIdProvider.cs new file mode 100644 index 00000000..561f16e2 --- /dev/null +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantServiceTenantIdProvider.cs @@ -0,0 +1,46 @@ +using System.Linq; + +namespace Backend.Fx.Environment.MultiTenancy +{ + /// + /// An implementation of that uses a directly. Keep in mind + /// that this + /// relies on direct access to the tenant service and therefore implicitly to the underlying tenant repository. When + /// multiple + /// applications do not share the same persistence mechanism or are distributed to multiple hosts, this implementation + /// cannot be + /// used, but must be implemented by a client to the master tenant service, probably using a remoting technology like + /// RESTful Service, + /// HTTP, gRPC or SOAP web service. + /// + public class TenantServiceTenantIdProvider : ITenantIdProvider + { + private readonly ITenantService _tenantService; + + public TenantServiceTenantIdProvider(ITenantService tenantService) + { + _tenantService = tenantService; + } + + public TenantId[] GetActiveDemonstrationTenantIds() + { + return _tenantService.GetActiveDemonstrationTenants() + .Select(t => new TenantId(t.Id)) + .ToArray(); + } + + public TenantId[] GetActiveProductionTenantIds() + { + return _tenantService.GetActiveProductionTenants() + .Select(t => new TenantId(t.Id)) + .ToArray(); + } + + public TenantId[] GetActiveTenantIds() + { + return _tenantService.GetActiveTenants() + .Select(t => new TenantId(t.Id)) + .ToArray(); + } + } +} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantStatusChanged.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantStatusChanged.cs index 571ee46e..74556226 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantStatusChanged.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantStatusChanged.cs @@ -4,7 +4,7 @@ namespace Backend.Fx.Environment.MultiTenancy { public abstract class TenantStatusChanged : IntegrationEvent { - protected TenantStatusChanged(int tenantId, string name, string description, bool isDemoTenant) : base(tenantId) + protected TenantStatusChanged(string name, string description, bool isDemoTenant) { Name = name; Description = description; @@ -17,4 +17,4 @@ protected TenantStatusChanged(int tenantId, string name, string description, boo public bool IsDemoTenant { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs b/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs index 94300a68..6482a480 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs @@ -1,4 +1,3 @@ -using System; using System.Threading; using System.Threading.Tasks; using Backend.Fx.Logging; @@ -10,14 +9,15 @@ namespace Backend.Fx.Environment.Persistence public class BackendFxDbApplication : IBackendFxApplication { private static readonly ILogger Logger = LogManager.Create(); - - private readonly IDatabaseBootstrapper _databaseBootstrapper; - private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; private readonly IBackendFxApplication _backendFxApplication; + private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; + + private readonly IDatabaseBootstrapper _databaseBootstrapper; - public BackendFxDbApplication(IDatabaseBootstrapper databaseBootstrapper, - IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, - IBackendFxApplication backendFxApplication) + public BackendFxDbApplication( + IDatabaseBootstrapper databaseBootstrapper, + IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, + IBackendFxApplication backendFxApplication) { _databaseBootstrapper = databaseBootstrapper; _databaseAvailabilityAwaiter = databaseAvailabilityAwaiter; @@ -39,13 +39,6 @@ public void Dispose() public IMessageBus MessageBus => _backendFxApplication.MessageBus; - public bool WaitForBoot(int timeoutMilliSeconds = Int32.MaxValue, CancellationToken cancellationToken = default) - { - Logger.Trace("Waiting for boot..."); - return _backendFxApplication.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); public async Task BootAsync(CancellationToken cancellationToken = default) { Logger.Trace("Booting..."); @@ -54,4 +47,4 @@ public async Task BootAsync(CancellationToken cancellationToken = default) await _backendFxApplication.BootAsync(cancellationToken); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs b/src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs index a29dbcc7..34d282c0 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs @@ -8,7 +8,8 @@ namespace Backend.Fx.Environment.Persistence public class DbConnectionOperationDecorator : IOperation { private static readonly ILogger Logger = LogManager.Create(); - private IDisposable _connectionLifetimeLogger; + private IDisposable _connectionLifetimeLogger; + public DbConnectionOperationDecorator(IDbConnection dbConnection, IOperation operation) { DbConnection = dbConnection; @@ -41,9 +42,9 @@ public void Cancel() Logger.Debug("Closing database connection"); DbConnection.Close(); _connectionLifetimeLogger?.Dispose(); - + // note: we do not dispose the DbConnection here, because we did not instantiate it. Disposing is always up to the creator of // the instance, that is in this case the injection container. } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs index 51b79d16..514e67ac 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs @@ -6,7 +6,8 @@ namespace Backend.Fx.Environment.Persistence { /// - /// Enriches the operation to use a database transaction during lifetime. The transaction gets started, before IOperation.Begin() + /// Enriches the operation to use a database transaction during lifetime. The transaction gets started, before + /// IOperation.Begin() /// is being called and gets committed after IOperation.Complete() is being called. /// public class DbTransactionOperationDecorator : IOperation @@ -14,17 +15,18 @@ public class DbTransactionOperationDecorator : IOperation private static readonly ILogger Logger = LogManager.Create(); private readonly IDbConnection _dbConnection; private readonly IOperation _operation; - private bool _shouldHandleConnectionState; private IsolationLevel _isolationLevel = IsolationLevel.Unspecified; - private IDisposable _transactionLifetimeLogger; + private bool _shouldHandleConnectionState; private TxState _state = TxState.NotStarted; - + private IDisposable _transactionLifetimeLogger; + public DbTransactionOperationDecorator(IDbConnection dbConnection, IOperation operation) { _dbConnection = dbConnection; _operation = operation; } + public IDbTransaction CurrentTransaction { get; private set; } public virtual void Begin() { @@ -47,8 +49,6 @@ public virtual void Begin() _operation.Begin(); } - public IDbTransaction CurrentTransaction { get; private set; } - public void Complete() { if (_state != TxState.Active) @@ -80,7 +80,7 @@ public void Cancel() { throw new InvalidOperationException($"Cannot roll back a transaction that is {_state}"); } - + _operation.Cancel(); CurrentTransaction.Rollback(); @@ -96,17 +96,18 @@ public void Cancel() _state = TxState.RolledBack; } - + public void SetIsolationLevel(IsolationLevel isolationLevel) { if (_state != TxState.NotStarted) { - throw new InvalidOperationException("Isolation level cannot be changed after the transaction has been started"); + throw new InvalidOperationException( + "Isolation level cannot be changed after the transaction has been started"); } _isolationLevel = isolationLevel; } - + private bool ShouldHandleConnectionState() { switch (_dbConnection.State) @@ -116,10 +117,12 @@ private bool ShouldHandleConnectionState() case ConnectionState.Open: return false; default: - throw new InvalidOperationException($"A connection provided to the operation must either be closed or open, but must not be {_dbConnection.State}"); + throw new InvalidOperationException( + $"A connection provided to the operation must either be closed or open, but must not be {_dbConnection.State}"); } } - + + private enum TxState { NotStarted, @@ -128,4 +131,4 @@ private enum TxState RolledBack } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs b/src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs index 791bc42e..378ca49c 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs @@ -6,11 +6,13 @@ namespace Backend.Fx.Environment.Persistence public class FlushDomainEventAggregatorDecorator : IDomainEventAggregator { private static readonly ILogger Logger = LogManager.Create(); - + private readonly ICanFlush _canFlush; private readonly IDomainEventAggregator _domainEventAggregatorImplementation; - public FlushDomainEventAggregatorDecorator(ICanFlush canFlush, IDomainEventAggregator domainEventAggregatorImplementation) + public FlushDomainEventAggregatorDecorator( + ICanFlush canFlush, + IDomainEventAggregator domainEventAggregatorImplementation) { _canFlush = canFlush; _domainEventAggregatorImplementation = domainEventAggregatorImplementation; @@ -28,4 +30,4 @@ public void RaiseEvents() _domainEventAggregatorImplementation.RaiseEvents(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs b/src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs index 3ee242f0..62be1511 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs @@ -6,8 +6,8 @@ namespace Backend.Fx.Environment.Persistence public class FlushOperationDecorator : IOperation { private static readonly ILogger Logger = LogManager.Create(); - private readonly IOperation _operationImplementation; private readonly ICanFlush _canFlush; + private readonly IOperation _operationImplementation; public FlushOperationDecorator(ICanFlush canFlush, IOperation operationImplementation) { @@ -32,4 +32,4 @@ public void Cancel() _operationImplementation.Cancel(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs b/src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs index 7e27683e..30e9782d 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs @@ -4,4 +4,4 @@ public interface ICanFlush { void Flush(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs b/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs index 7b21926e..93c476e3 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs @@ -7,4 +7,4 @@ public interface IDatabaseAvailabilityAwaiter { Task WaitForDatabase(CancellationToken cancellationToken); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs b/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs index 65f1049a..57910d3d 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs @@ -3,10 +3,11 @@ namespace Backend.Fx.Environment.Persistence { /// - /// Encapsulates database bootstrapping. This interface hides the implementation details for creating/migrating the database + /// Encapsulates database bootstrapping. This interface hides the implementation details for creating/migrating the + /// database /// public interface IDatabaseBootstrapper : IDisposable { void EnsureDatabaseExistence(); } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/IDbConnectionFactory.cs b/src/abstractions/Backend.Fx/Environment/Persistence/IDbConnectionFactory.cs similarity index 53% rename from src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/IDbConnectionFactory.cs rename to src/abstractions/Backend.Fx/Environment/Persistence/IDbConnectionFactory.cs index df953115..da187f9a 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/IDbConnectionFactory.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/IDbConnectionFactory.cs @@ -1,9 +1,9 @@ -using System.Data; +using System.Data; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.Environment.Persistence { public interface IDbConnectionFactory { IDbConnection Create(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/IPersistenceSession.cs b/src/abstractions/Backend.Fx/Environment/Persistence/IPersistenceSession.cs new file mode 100644 index 00000000..955f2f3d --- /dev/null +++ b/src/abstractions/Backend.Fx/Environment/Persistence/IPersistenceSession.cs @@ -0,0 +1,15 @@ +using System.Security.Principal; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.Environment.Persistence +{ + public interface IPersistenceSession : ICanFlush + { + ICurrentTHolder IdentityHolder { get; } + + IClock Clock { get; } + + void MakeReadonly(); + } +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Mssql/MsSqlSequence.cs b/src/abstractions/Backend.Fx/Environment/Persistence/MsSqlSequence.cs similarity index 68% rename from src/implementations/Backend.Fx.EfCorePersistence/Mssql/MsSqlSequence.cs rename to src/abstractions/Backend.Fx/Environment/Persistence/MsSqlSequence.cs index bceb0869..94b76d4e 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Mssql/MsSqlSequence.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/MsSqlSequence.cs @@ -1,10 +1,8 @@ using System; -using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; -namespace Backend.Fx.EfCorePersistence.Mssql +namespace Backend.Fx.Environment.Persistence { public abstract class MsSqlSequence : ISequence { @@ -17,19 +15,23 @@ protected MsSqlSequence(IDbConnectionFactory dbConnectionFactory) } protected abstract string SequenceName { get; } + protected virtual string SchemaName { get; } = "dbo"; + protected virtual int StartValue => 1; + public void EnsureSequence() { Logger.Info($"Ensuring existence of mssql sequence {SchemaName}.{SequenceName}"); - using (IDbConnection dbConnection = _dbConnectionFactory.Create()) + using (var dbConnection = _dbConnectionFactory.Create()) { dbConnection.Open(); bool sequenceExists; - using (IDbCommand cmd = dbConnection.CreateCommand()) + using (var cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"SELECT count(*) FROM sys.sequences seq join sys.schemas s on s.schema_id = seq.schema_id WHERE seq.name = '{SequenceName}' and s.name = '{SchemaName}'"; - sequenceExists = (int) cmd.ExecuteScalar() == 1; + cmd.CommandText + = $"SELECT count(*) FROM sys.sequences seq join sys.schemas s on s.schema_id = seq.schema_id WHERE seq.name = '{SequenceName}' and s.name = '{SchemaName}'"; + sequenceExists = (int)cmd.ExecuteScalar() == 1; } if (sequenceExists) @@ -39,9 +41,10 @@ public void EnsureSequence() else { Logger.Info($"Sequence {SchemaName}.{SequenceName} does not exist yet and will be created now"); - using (IDbCommand cmd = dbConnection.CreateCommand()) + using (var cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"CREATE SEQUENCE [{SchemaName}].[{SequenceName}] START WITH 1 INCREMENT BY {Increment}"; + cmd.CommandText + = $"CREATE SEQUENCE [{SchemaName}].[{SequenceName}] START WITH {StartValue} INCREMENT BY {Increment}"; cmd.ExecuteNonQuery(); Logger.Info($"Sequence {SchemaName}.{SequenceName} created"); } @@ -51,11 +54,11 @@ public void EnsureSequence() public int GetNextValue() { - using (IDbConnection dbConnection = _dbConnectionFactory.Create()) + using (var dbConnection = _dbConnectionFactory.Create()) { dbConnection.Open(); int nextValue; - using (IDbCommand selectNextValCommand = dbConnection.CreateCommand()) + using (var selectNextValCommand = dbConnection.CreateCommand()) { selectNextValCommand.CommandText = $"SELECT next value FOR {SchemaName}.{SequenceName}"; nextValue = Convert.ToInt32(selectNextValCommand.ExecuteScalar()); @@ -68,4 +71,4 @@ public int GetNextValue() public abstract int Increment { get; } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Oracle/OracleSequence.cs b/src/abstractions/Backend.Fx/Environment/Persistence/OracleSequence.cs similarity index 72% rename from src/implementations/Backend.Fx.EfCorePersistence/Oracle/OracleSequence.cs rename to src/abstractions/Backend.Fx/Environment/Persistence/OracleSequence.cs index 93d58eb3..fef01d04 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Oracle/OracleSequence.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/OracleSequence.cs @@ -1,10 +1,8 @@ using System; -using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; -namespace Backend.Fx.EfCorePersistence.Oracle +namespace Backend.Fx.Environment.Persistence { public abstract class OracleSequence : ISequence { @@ -17,13 +15,19 @@ protected OracleSequence(IDbConnectionFactory dbConnectionFactory) } protected abstract string SequenceName { get; } + protected abstract string SchemaName { get; } + protected virtual int StartValue => 1; + private string SchemaPrefix { get { - if (string.IsNullOrEmpty(SchemaName)) return string.Empty; + if (string.IsNullOrEmpty(SchemaName)) + { + return string.Empty; + } return SchemaName + "."; } @@ -33,14 +37,14 @@ public void EnsureSequence() { Logger.Info($"Ensuring existence of oracle sequence {SchemaPrefix}{SequenceName}"); - using (IDbConnection dbConnection = _dbConnectionFactory.Create()) + using (var dbConnection = _dbConnectionFactory.Create()) { dbConnection.Open(); bool sequenceExists; - using (IDbCommand command = dbConnection.CreateCommand()) + using (var command = dbConnection.CreateCommand()) { command.CommandText = $"SELECT count(*) FROM user_sequences WHERE sequence_name = '{SequenceName}'"; - sequenceExists = (decimal) command.ExecuteScalar() == 1; + sequenceExists = (decimal)command.ExecuteScalar() == 1; } if (sequenceExists) @@ -50,9 +54,10 @@ public void EnsureSequence() else { Logger.Info($"Sequence {SchemaPrefix}{SequenceName} does not exist yet and will be created now"); - using (IDbCommand cmd = dbConnection.CreateCommand()) + using (var cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"CREATE SEQUENCE {SchemaPrefix}{SequenceName} START WITH 1 INCREMENT BY {Increment}"; + cmd.CommandText + = $"CREATE SEQUENCE {SchemaPrefix}{SequenceName} START WITH {StartValue} INCREMENT BY {Increment}"; cmd.ExecuteNonQuery(); Logger.Info($"Sequence {SchemaPrefix}{SequenceName} created"); } @@ -62,12 +67,12 @@ public void EnsureSequence() public int GetNextValue() { - using (IDbConnection dbConnection = _dbConnectionFactory.Create()) + using (var dbConnection = _dbConnectionFactory.Create()) { dbConnection.Open(); int nextValue; - using (IDbCommand command = dbConnection.CreateCommand()) + using (var command = dbConnection.CreateCommand()) { command.CommandText = $"SELECT {SchemaPrefix}{SequenceName}.NEXTVAL FROM dual"; nextValue = Convert.ToInt32(command.ExecuteScalar()); @@ -80,4 +85,4 @@ public int GetNextValue() public abstract int Increment { get; } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Postgres/PostgresSequence.cs b/src/abstractions/Backend.Fx/Environment/Persistence/PostgresSequence.cs similarity index 68% rename from src/implementations/Backend.Fx.EfCorePersistence/Postgres/PostgresSequence.cs rename to src/abstractions/Backend.Fx/Environment/Persistence/PostgresSequence.cs index a1bc6364..cc36bee9 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Postgres/PostgresSequence.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/PostgresSequence.cs @@ -1,10 +1,8 @@ using System; -using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; -namespace Backend.Fx.EfCorePersistence.Postgres +namespace Backend.Fx.Environment.Persistence { public abstract class PostgresSequence : ISequence { @@ -17,20 +15,24 @@ protected PostgresSequence(IDbConnectionFactory dbConnectionFactory) } protected abstract string SequenceName { get; } + protected abstract string SchemaName { get; } + protected virtual int StartValue => 1; + public void EnsureSequence() { Logger.Info($"Ensuring existence of postgres sequence {SchemaName}.{SequenceName}"); - using (IDbConnection dbConnection = _dbConnectionFactory.Create()) + using (var dbConnection = _dbConnectionFactory.Create()) { dbConnection.Open(); bool sequenceExists; - using (IDbCommand command = dbConnection.CreateCommand()) + using (var command = dbConnection.CreateCommand()) { - command.CommandText = $"SELECT count(*) FROM information_schema.sequences WHERE sequence_name = '{SequenceName}' AND sequence_schema = '{SchemaName}'"; - sequenceExists = (long) command.ExecuteScalar() == 1L; + command.CommandText + = $"SELECT count(*) FROM information_schema.sequences WHERE sequence_name = '{SequenceName}' AND sequence_schema = '{SchemaName}'"; + sequenceExists = (long)command.ExecuteScalar() == 1L; } if (sequenceExists) @@ -40,9 +42,10 @@ public void EnsureSequence() else { Logger.Info($"Sequence {SchemaName}.{SequenceName} does not exist yet and will be created now"); - using (IDbCommand cmd = dbConnection.CreateCommand()) + using (var cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"CREATE SEQUENCE {SchemaName}.{SequenceName} START WITH 1 INCREMENT BY {Increment}"; + cmd.CommandText + = $"CREATE SEQUENCE {SchemaName}.{SequenceName} START WITH {StartValue} INCREMENT BY {Increment}"; cmd.ExecuteNonQuery(); Logger.Info($"Sequence {SchemaName}.{SequenceName} created"); } @@ -52,12 +55,12 @@ public void EnsureSequence() public int GetNextValue() { - using (IDbConnection dbConnection = _dbConnectionFactory.Create()) + using (var dbConnection = _dbConnectionFactory.Create()) { dbConnection.Open(); int nextValue; - using (IDbCommand command = dbConnection.CreateCommand()) + using (var command = dbConnection.CreateCommand()) { command.CommandText = $"SELECT nextval('{SchemaName}.{SequenceName}');"; nextValue = Convert.ToInt32(command.ExecuteScalar()); @@ -70,4 +73,4 @@ public int GetNextValue() public abstract int Increment { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs index fd26b42e..59b2e77e 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs @@ -29,4 +29,4 @@ public void Cancel() _operationImplementation.Cancel(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/ClientException.cs b/src/abstractions/Backend.Fx/Exceptions/ClientException.cs index dc32894d..90cdc5ed 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ClientException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ClientException.cs @@ -9,26 +9,30 @@ public class ClientException : Exception { public ClientException() : base("Bad request.") - { - } + { } - /// When using one of the middlewares in Backend.Fx.AspNetCore.ErrorHandling, the message is not sent - /// to the client to not provide internal details to an attacker. Write the exception message with a developer in mind, since + /// + /// When using one of the middlewares in Backend.Fx.AspNetCore.ErrorHandling, the message is not sent + /// to the client to not provide internal details to an attacker. Write the exception message with a developer in mind, + /// since /// the application log will contain the message. To provide the user with functional feedback to correct their input, use - /// the AddError(s) overloads. + /// the AddError(s) overloads. + /// public ClientException(string message) : base(message) - { - } + { } - /// When using one of the middlewares in Backend.Fx.AspNetCore.ErrorHandling, the message is not sent - /// to the client to not provide internal details to an attacker. Write the exception message with a developer in mind, since + /// + /// When using one of the middlewares in Backend.Fx.AspNetCore.ErrorHandling, the message is not sent + /// to the client to not provide internal details to an attacker. Write the exception message with a developer in mind, + /// since /// the application log will contain the message. To provide the user with functional feedback to correct their input, use - /// the AddError(s) overloads. + /// the AddError(s) overloads. + /// + /// public ClientException(string message, Exception innerException) : base(message, innerException) - { - } + { } public Errors Errors { get; } = new Errors(); @@ -40,27 +44,30 @@ public bool HasErrors() /// public override string ToString() { - string exceptionType = GetType().ToString(); + var exceptionType = GetType().ToString(); string message = string.IsNullOrEmpty(Message) - ? exceptionType - : exceptionType + ": " + Message; + ? exceptionType + : exceptionType + ": " + Message; string innerException = InnerException != null - ? " ---> " - + InnerException - + System.Environment.NewLine - + " End of inner exception stack trace" - : null; + ? " ---> " + + InnerException + + System.Environment.NewLine + + " End of inner exception stack trace" + : null; - return string.Join(System.Environment.NewLine, - new[] {message, Errors.ToString(), innerException, StackTrace}.Where(s => s != null)); + return string.Join( + System.Environment.NewLine, + new[] { message, Errors.ToString(), innerException, StackTrace }.Where(s => s != null)); } - + /// - /// Used to build an with multiple possible error messages. The builder will throw on disposal - /// when at least one error was added. Using the AddIf methods is quite comfortable when there are several criteria to be validated - /// before executing a business case. + /// Used to build an with multiple possible error messages. The builder will throw on + /// disposal + /// when at least one error was added. Using the AddIf methods is quite comfortable when there are several criteria to be + /// validated + /// before executing a business case. /// public static IExceptionBuilder UseBuilder() { @@ -68,31 +75,40 @@ public static IExceptionBuilder UseBuilder() } } + public static class ClientExceptionEx { - public static TEx AddError(this TEx clientException, [LocalizationRequired] string errorMessage) where TEx : ClientException + public static TEx AddError(this TEx clientException, [LocalizationRequired] string errorMessage) + where TEx : ClientException { clientException.Errors.Add(errorMessage); return clientException; } - public static TEx AddError(this TEx clientException, string key, [LocalizationRequired] string errorMessage) where TEx : ClientException + public static TEx AddError( + this TEx clientException, + string key, + [LocalizationRequired] string errorMessage) where TEx : ClientException { clientException.Errors.Add(key, errorMessage); return clientException; } - public static TEx AddErrors(this TEx clientException, [LocalizationRequired] IEnumerable errorMessages) where TEx : ClientException + public static TEx AddErrors( + this TEx clientException, + [LocalizationRequired] IEnumerable errorMessages) where TEx : ClientException { clientException.Errors.Add(errorMessages); return clientException; } - - public static TEx AddErrors(this TEx clientException, string key, [LocalizationRequired] IEnumerable errorMessages) where TEx : ClientException + public static TEx AddErrors( + this TEx clientException, + string key, + [LocalizationRequired] IEnumerable errorMessages) where TEx : ClientException { clientException.Errors.Add(key, errorMessages); return clientException; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs b/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs index 541d5ef9..b4d4851b 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs @@ -6,19 +6,16 @@ public class ConflictedException : ClientException { public ConflictedException() : base("Conflicted") - { - } + { } /// public ConflictedException(string message) : base(message) - { - } + { } /// public ConflictedException(string message, Exception innerException) : base(message, innerException) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/Errors.cs b/src/abstractions/Backend.Fx/Exceptions/Errors.cs index 1aeae363..ac3e2fa3 100644 --- a/src/abstractions/Backend.Fx/Exceptions/Errors.cs +++ b/src/abstractions/Backend.Fx/Exceptions/Errors.cs @@ -8,7 +8,9 @@ namespace Backend.Fx.Exceptions public class Errors : IReadOnlyDictionary { private const string GenericErrorKey = ""; - private readonly IDictionary> _dictionaryImplementation = new Dictionary>(); + + private readonly IDictionary> _dictionaryImplementation + = new Dictionary>(); public bool ContainsKey(string key) { @@ -36,6 +38,20 @@ public IEnumerable Values get { return _dictionaryImplementation.Values.Select(errors => errors.ToArray()); } } + public IEnumerator> GetEnumerator() + { + return _dictionaryImplementation + .Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.ToArray())) + .GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public int Count => _dictionaryImplementation.Count; + public Errors Add(string errorMessage) { Add(GenericErrorKey, errorMessage); @@ -73,18 +89,6 @@ public Errors Add(string key, string error) return this; } - - public IEnumerator> GetEnumerator() - { - return _dictionaryImplementation.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.ToArray())).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public int Count => _dictionaryImplementation.Count; public override string ToString() { @@ -93,14 +97,14 @@ public override string ToString() b.Append(Count.ToString()); b.AppendLine(); - foreach (var keyValuePair in this) + foreach (KeyValuePair keyValuePair in this) { b.Append(" "); - b.Append(string.IsNullOrEmpty(keyValuePair.Key) ? "(generic)": keyValuePair.Key); + b.Append(string.IsNullOrEmpty(keyValuePair.Key) ? "(generic)" : keyValuePair.Key); b.AppendLine(); for (var index = 0; index < keyValuePair.Value.Length; index++) { - var error = keyValuePair.Value[index]; + string error = keyValuePair.Value[index]; b.Append(" "); b.Append($"[{index}]".PadLeft(4)); b.Append(" "); @@ -112,4 +116,4 @@ public override string ToString() return b.ToString(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs b/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs index 79084a40..5945b095 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs @@ -11,6 +11,7 @@ public interface IExceptionBuilder : IDisposable void AddIf(string key, bool condition, string error); } + public class ExceptionBuilder : IExceptionBuilder where TEx : ClientException, new() { private readonly TEx _clientException = new TEx(); @@ -57,4 +58,4 @@ public void AddIf(string key, bool condition, string error) } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs b/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs index e5db75a1..0aaaadd0 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs @@ -6,19 +6,16 @@ public class ForbiddenException : ClientException { public ForbiddenException() : base("Unauthorized") - { - } + { } /// public ForbiddenException(string message) : base(message) - { - } + { } /// public ForbiddenException(string message, Exception innerException) : base(message, innerException) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs b/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs index cc3b2350..bf166331 100644 --- a/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs @@ -2,14 +2,9 @@ { public class NotFoundException : ClientException { - public string EntityName { get; } - - public object Id { get; } - public NotFoundException() : base("Not found.") - { - } + { } public NotFoundException(string entityName, object id) : base($"No {entityName}[{id}] found.") @@ -17,13 +12,17 @@ public NotFoundException(string entityName, object id) EntityName = entityName; Id = id; } + + public string EntityName { get; } + + public object Id { get; } } - + + public class NotFoundException : NotFoundException { public NotFoundException(object id) : base(typeof(TEntity).Name, id) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs b/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs index 42dd5f7c..dfc03521 100644 --- a/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs @@ -17,11 +17,13 @@ public TooManyRequestsException(int retryAfter, string message) : base(message) } /// - public TooManyRequestsException(int retryAfter, string message, Exception innerException) : base(message, innerException) + public TooManyRequestsException(int retryAfter, string message, Exception innerException) : base( + message, + innerException) { RetryAfter = retryAfter; } public int RetryAfter { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs b/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs index f365a9f2..c428f1d4 100644 --- a/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs @@ -6,19 +6,16 @@ public class UnauthorizedException : ClientException { public UnauthorizedException() : base("Unauthorized") - { - } + { } /// public UnauthorizedException(string message) : base(message) - { - } + { } /// public UnauthorizedException(string message, Exception innerException) : base(message, innerException) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs b/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs index 018108a6..7e9b8425 100644 --- a/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs @@ -6,29 +6,28 @@ public class UnprocessableException : ClientException { public UnprocessableException() : base("The provided arguments could not be processed.") - { - } + { } /// public UnprocessableException(string message) : base(message) - { - } + { } /// public UnprocessableException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// - /// Used to build an with multiple possible error messages. The builder will throw on disposal - /// when at least one error was added. Using the AddIf methods is quite comfortable when there are several criteria to be validated - /// before executing a business case. + /// Used to build an with multiple possible error messages. The builder will throw on + /// disposal + /// when at least one error was added. Using the AddIf methods is quite comfortable when there are several criteria to be + /// validated + /// before executing a business case. /// public new static IExceptionBuilder UseBuilder() { return new ExceptionBuilder(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs b/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs index 46bf8915..1ebb2561 100644 --- a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs +++ b/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs @@ -17,28 +17,31 @@ public static class AsyncHelper /// public static void RunSync(Func task) { - SynchronizationContext oldContext = SynchronizationContext.Current; + var oldContext = SynchronizationContext.Current; try { - var synch = new ExclusiveSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(synch); - synch.Post(async _ => - { - try - { - await task(); - } - catch (Exception e) - { - synch.InnerException = e; - throw; - } - finally + var syncContext = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(syncContext); + syncContext.Post( + // ReSharper disable once AsyncVoidLambda + async _ => { - synch.EndMessageLoop(); - } - }, null); - synch.BeginMessageLoop(); + try + { + await task(); + } + catch (Exception e) + { + syncContext.InnerException = e; + throw; + } + finally + { + syncContext.EndMessageLoop(); + } + }, + null); + syncContext.BeginMessageLoop(); } finally { @@ -55,27 +58,30 @@ public static void RunSync(Func task) public static T RunSync(Func> task) { var ret = default(T); - SynchronizationContext oldContext = SynchronizationContext.Current; + var oldContext = SynchronizationContext.Current; try { var exclusiveSynchronizationContext = new ExclusiveSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(exclusiveSynchronizationContext); - exclusiveSynchronizationContext.Post(async _ => - { - try - { - ret = await task(); - } - catch (Exception e) + exclusiveSynchronizationContext.Post( + // ReSharper disable once AsyncVoidLambda + async _ => { - exclusiveSynchronizationContext.InnerException = e; - throw; - } - finally - { - exclusiveSynchronizationContext.EndMessageLoop(); - } - }, null); + try + { + ret = await task(); + } + catch (Exception e) + { + exclusiveSynchronizationContext.InnerException = e; + throw; + } + finally + { + exclusiveSynchronizationContext.EndMessageLoop(); + } + }, + null); exclusiveSynchronizationContext.BeginMessageLoop(); } finally @@ -86,12 +92,16 @@ public static T RunSync(Func> task) return ret; } + private class ExclusiveSynchronizationContext : SynchronizationContext { + private readonly Queue> _items + = new Queue>(); + + private readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false); private bool _done; + public Exception InnerException { private get; set; } - private readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false); - private readonly Queue> _items = new Queue>(); public override void Send(SendOrPostCallback d, object state) { @@ -147,4 +157,4 @@ public override SynchronizationContext CreateCopy() } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs b/src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs index 0d7ad38d..99a3b661 100644 --- a/src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs @@ -1,7 +1,7 @@ -namespace Backend.Fx.Extensions -{ - using System; +using System; +namespace Backend.Fx.Extensions +{ public static class DateTimeEx { /// @@ -21,7 +21,10 @@ public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek = Day return dt.AddDays(-1 * diff).Date; } - public static DateTime GetWeekDay(this DateTime dt, DayOfWeek dayOfWeek, DayOfWeek startOfWeek = DayOfWeek.Monday) + public static DateTime GetWeekDay( + this DateTime dt, + DayOfWeek dayOfWeek, + DayOfWeek startOfWeek = DayOfWeek.Monday) { dt = dt.StartOfWeek(startOfWeek); while (dt.DayOfWeek != dayOfWeek) @@ -34,7 +37,7 @@ public static DateTime GetWeekDay(this DateTime dt, DayOfWeek dayOfWeek, DayOfWe public static long ToUnixEpochDate(this DateTime utcDate) { - return (long) Math.Round((utcDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds); + return (long)Math.Round((utcDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs b/src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs index 562b56da..394fcfdd 100644 --- a/src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs +++ b/src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs @@ -17,4 +17,4 @@ public void Dispose() _onDisposal(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs b/src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs index 97dfb7c6..2b34a578 100644 --- a/src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs @@ -1,16 +1,16 @@ -namespace Backend.Fx.Extensions -{ - using System; - using System.Collections.Generic; +using System; +using System.Collections.Generic; +namespace Backend.Fx.Extensions +{ public static class EnumerableEx { public static void ForAll(this IEnumerable enumerable, Action action) { - foreach (T item in enumerable) + foreach (var item in enumerable) { action(item); } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs b/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs index df5b80d5..0190f36a 100644 --- a/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs +++ b/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs @@ -13,10 +13,10 @@ public MultipleDisposable(params IDisposable[] disposables) public void Dispose() { - foreach (IDisposable disposable in _disposables) + foreach (var disposable in _disposables) { disposable?.Dispose(); } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs b/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs index 7ad0645d..c1ff2e88 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs +++ b/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs @@ -5,6 +5,17 @@ namespace Backend.Fx.Extensions { public static class ReaderWriterLockSlimExtensions { + public static IDisposable Read(this ReaderWriterLockSlim obj) + { + return new ReadLockToken(obj); + } + + public static IDisposable Write(this ReaderWriterLockSlim obj) + { + return new WriteLockToken(obj); + } + + private sealed class ReadLockToken : IDisposable { private ReaderWriterLockSlim _sync; @@ -25,6 +36,7 @@ public void Dispose() } } + private sealed class WriteLockToken : IDisposable { private ReaderWriterLockSlim _sync; @@ -44,15 +56,5 @@ public void Dispose() } } } - - public static IDisposable Read(this ReaderWriterLockSlim obj) - { - return new ReadLockToken(obj); - } - - public static IDisposable Write(this ReaderWriterLockSlim obj) - { - return new WriteLockToken(obj); - } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs index 1d357fa8..6d4ac1c4 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -8,7 +9,8 @@ public static class ReflectionEx { public static bool IsImplementationOfOpenGenericInterface(this Type t, Type openGenericInterface) { - return t.GetInterfaces().Any(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == openGenericInterface); + return t.GetInterfaces() + .Any(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == openGenericInterface); } public static string GetDetailedTypeName(this Type t) @@ -16,12 +18,12 @@ public static string GetDetailedTypeName(this Type t) string detailedTypeName = t.Name; if (t.GetTypeInfo().IsGenericType) { - var genericNameWithoutArgCount = t.Name.Substring(0, t.Name.IndexOf('`')); - var typeArgNames = t.GenericTypeArguments.Select(a => a.Name); + string genericNameWithoutArgCount = t.Name.Substring(0, t.Name.IndexOf('`')); + IEnumerable typeArgNames = t.GenericTypeArguments.Select(a => a.Name); detailedTypeName = $"{genericNameWithoutArgCount}<{string.Join(",", typeArgNames)}>"; } return detailedTypeName; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs b/src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs index de594405..fddae22f 100644 --- a/src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs +++ b/src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; namespace Backend.Fx.Extensions @@ -12,9 +13,10 @@ public static TEnum Parse(this string value) where TEnum : struct return enumValue; } - var validValues = Enum.GetValues(typeof(TEnum)).Cast(); + IEnumerable validValues = Enum.GetValues(typeof(TEnum)).Cast(); string validValuesString = string.Join("], [", validValues.Select(en => en.ToString())); - throw new ArgumentException($"The string [{value}] is not a valid value for the enum type {typeof(TEnum).Name}. Valid string values are: [{validValuesString}]"); + throw new ArgumentException( + $"The string [{value}] is not a valid value for the enum type {typeof(TEnum).Name}. Valid string values are: [{validValuesString}]"); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/StringEx.cs b/src/abstractions/Backend.Fx/Extensions/StringEx.cs index 54091267..464c7643 100644 --- a/src/abstractions/Backend.Fx/Extensions/StringEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/StringEx.cs @@ -29,4 +29,4 @@ public static string ToMacintoshLineEnding(this string s) return Regex.Replace(s, @"\r?\n", "\r"); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs b/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs index cb401099..a648fdb2 100644 --- a/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs +++ b/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs @@ -14,9 +14,15 @@ public TolerantDateTimeOffsetComparer(TimeSpan epsilon) public bool Equals(DateTimeOffset? x, DateTimeOffset? y) { - if (x == null && y == null) return true; + if (x == null && y == null) + { + return true; + } - if (x == null || y == null) return false; + if (x == null || y == null) + { + return false; + } return Math.Abs((x.Value - y.Value).TotalMilliseconds) < _epsilon.TotalMilliseconds; } @@ -27,6 +33,7 @@ public int GetHashCode(DateTimeOffset? obj) } } + public class TolerantDateTimeComparer : IEqualityComparer { private readonly TimeSpan _epsilon; @@ -38,9 +45,15 @@ public TolerantDateTimeComparer(TimeSpan epsilon) public bool Equals(DateTime? x, DateTime? y) { - if (x == null && y == null) return true; + if (x == null && y == null) + { + return true; + } - if (x == null || y == null) return false; + if (x == null || y == null) + { + return false; + } return Math.Abs((x.Value - y.Value).TotalMilliseconds) < _epsilon.TotalMilliseconds; } @@ -50,4 +63,4 @@ public int GetHashCode(DateTime? obj) return obj?.GetHashCode() ?? 0; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Hacking/PrivateSetterCaller.cs b/src/abstractions/Backend.Fx/Hacking/PrivateSetterCaller.cs index db95c07b..660a9356 100644 --- a/src/abstractions/Backend.Fx/Hacking/PrivateSetterCaller.cs +++ b/src/abstractions/Backend.Fx/Hacking/PrivateSetterCaller.cs @@ -7,16 +7,22 @@ namespace Backend.Fx.Hacking { public static class PrivateSetterCaller { - public static void SetPrivate(this T instance, Expression> propertyExpression, TValue value) + public static void SetPrivate( + this T instance, + Expression> propertyExpression, + TValue value) { - instance.GetType().GetTypeInfo().GetDeclaredProperty(GetName(propertyExpression)).SetValue(instance, value, null); + instance.GetType() + .GetTypeInfo() + .GetDeclaredProperty(GetName(propertyExpression)) + .SetValue(instance, value, null); } private static string GetName(Expression> exp) { if (!(exp.Body is MemberExpression body)) { - var ubody = (UnaryExpression) exp.Body; + var ubody = (UnaryExpression)exp.Body; body = ubody.Operand as MemberExpression; } @@ -24,4 +30,4 @@ private static string GetName(Expression> exp) return body.Member.Name; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs b/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs index a2cb535c..2847fe52 100644 --- a/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs +++ b/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs @@ -8,14 +8,15 @@ public static class PrivateUtil { public static T CreateInstanceFromPrivateDefaultConstructor() { - ConstructorInfo constructor = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).SingleOrDefault(ci => ci.GetParameters().Length == 0); + var constructor = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance) + .SingleOrDefault(ci => ci.GetParameters().Length == 0); if (constructor == null) { throw new InvalidOperationException($"No private default constructor found in {typeof(T).Name}"); } - var instance = (T) constructor.Invoke(null); + var instance = (T)constructor.Invoke(null); return instance; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.NetCore/Logging/BackendFxLoggerProvider.cs b/src/abstractions/Backend.Fx/Logging/BackendFxLoggerProvider.cs similarity index 81% rename from src/environments/Backend.Fx.NetCore/Logging/BackendFxLoggerProvider.cs rename to src/abstractions/Backend.Fx/Logging/BackendFxLoggerProvider.cs index 4b9d7ead..fc46e5ff 100644 --- a/src/environments/Backend.Fx.NetCore/Logging/BackendFxLoggerProvider.cs +++ b/src/abstractions/Backend.Fx/Logging/BackendFxLoggerProvider.cs @@ -1,8 +1,7 @@ using System.Diagnostics; -using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -namespace Backend.Fx.NetCore.Logging +namespace Backend.Fx.Logging { [DebuggerStepThrough] public class BackendFxLoggerProvider : ILoggerProvider @@ -13,7 +12,6 @@ public Microsoft.Extensions.Logging.ILogger CreateLogger(string name) } public void Dispose() - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs b/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs index 8db9eee5..0b4b01a5 100644 --- a/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs @@ -18,4 +18,4 @@ public void LogException(Exception exception) } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/DebugLogger.cs b/src/abstractions/Backend.Fx/Logging/DebugLogger.cs index 7e2b408b..776e7dda 100644 --- a/src/abstractions/Backend.Fx/Logging/DebugLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DebugLogger.cs @@ -170,4 +170,4 @@ private void PrintToDebug(string format, params object[] args) System.Diagnostics.Debug.WriteLine(_type + format, args); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/DebugLoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/DebugLoggerFactory.cs index 5dd6a59a..71328de0 100644 --- a/src/abstractions/Backend.Fx/Logging/DebugLoggerFactory.cs +++ b/src/abstractions/Backend.Fx/Logging/DebugLoggerFactory.cs @@ -14,7 +14,7 @@ public ILogger Create(string s) public ILogger Create(Type t) { string s = t.FullName; - var indexOf = s?.IndexOf('[') ?? 0; + int indexOf = s?.IndexOf('[') ?? 0; if (indexOf > 0) { s = s?.Substring(0, indexOf); @@ -34,7 +34,6 @@ public void BeginActivity(int activityIndex) } public void Shutdown() - { - } + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs index d8c36e38..673e1ce5 100644 --- a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs @@ -13,8 +13,7 @@ public class DurationLogger : IDisposable public DurationLogger(Action logAction, string activity) : this(logAction, activity, activity) - { - } + { } public DurationLogger(Action logAction, string beginMessage, string endMessage) { @@ -45,4 +44,4 @@ private static string FormatDuration(string activity, TimeSpan duration) return $"{activity} - Duration: {duration:g}"; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs b/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs index 2d4d1859..6a665ea6 100644 --- a/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs +++ b/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs @@ -42,4 +42,4 @@ public static IEnumerable GetaAllMessages(this Exception exception) return exception.FromHierarchy(ex => ex.InnerException).Select(ex => ex.Message); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs b/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs index aec1ef44..bcd90212 100644 --- a/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs +++ b/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs @@ -14,24 +14,9 @@ public ExceptionLoggers() public ExceptionLoggers(params IExceptionLogger[] exceptionLoggers) { - foreach (IExceptionLogger exceptionLogger in exceptionLoggers) + foreach (var exceptionLogger in exceptionLoggers) { - _collectionImplementation.Add(exceptionLogger); - } - } - - public void LogException(Exception ex) - { - foreach (IExceptionLogger exceptionLogger in _collectionImplementation) - { - try - { - exceptionLogger.LogException(ex); - } - catch (Exception ex2) - { - Logger.Error(ex, $"{exceptionLogger.GetType().Name} failed to log the {ex2.GetType()} with message {ex.Message}"); - } + _collectionImplementation.Add(exceptionLogger); } } @@ -42,7 +27,7 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable) _collectionImplementation).GetEnumerator(); + return ((IEnumerable)_collectionImplementation).GetEnumerator(); } public void Add(IExceptionLogger item) @@ -73,5 +58,22 @@ public bool Remove(IExceptionLogger item) public int Count => _collectionImplementation.Count; public bool IsReadOnly => _collectionImplementation.IsReadOnly; + + public void LogException(Exception ex) + { + foreach (var exceptionLogger in _collectionImplementation) + { + try + { + exceptionLogger.LogException(ex); + } + catch (Exception ex2) + { + Logger.Error( + ex, + $"{exceptionLogger.GetType().Name} failed to log the {ex2.GetType()} with message {ex.Message}"); + } + } + } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.NetCore/Logging/FrameworkToBackendFxLogger.cs b/src/abstractions/Backend.Fx/Logging/FrameworkToBackendFxLogger.cs similarity index 83% rename from src/environments/Backend.Fx.NetCore/Logging/FrameworkToBackendFxLogger.cs rename to src/abstractions/Backend.Fx/Logging/FrameworkToBackendFxLogger.cs index a7336d7a..5cbeb10f 100644 --- a/src/environments/Backend.Fx.NetCore/Logging/FrameworkToBackendFxLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/FrameworkToBackendFxLogger.cs @@ -1,11 +1,12 @@ using System; using System.Diagnostics; -using Backend.Fx.Logging; +using Microsoft.Extensions.Logging; -namespace Backend.Fx.NetCore.Logging +namespace Backend.Fx.Logging { using NetFxILogger = Microsoft.Extensions.Logging.ILogger; - using NetFxLogLevel = Microsoft.Extensions.Logging.LogLevel; + using NetFxLogLevel = LogLevel; + [DebuggerStepThrough] public class FrameworkToBackendFxLogger : NetFxILogger @@ -17,9 +18,14 @@ public FrameworkToBackendFxLogger(ILogger logger) _logger = logger; } - public void Log(NetFxLogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, Exception exception, Func formatter) + public void Log( + NetFxLogLevel logLevel, + EventId eventId, + TState state, + Exception exception, + Func formatter) { - var formatted = formatter(state, exception); + string formatted = formatter(state, exception); formatted = formatted?.Replace("{", "{{").Replace("}", "}}"); switch (logLevel) @@ -68,4 +74,4 @@ public IDisposable BeginScope(TState state) return _logger.InfoDuration(state.ToString()); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.NetCore/Logging/FrameworkToBackendFxLoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/FrameworkToBackendFxLoggerFactory.cs similarity index 67% rename from src/environments/Backend.Fx.NetCore/Logging/FrameworkToBackendFxLoggerFactory.cs rename to src/abstractions/Backend.Fx/Logging/FrameworkToBackendFxLoggerFactory.cs index eb2de97f..554c724e 100644 --- a/src/environments/Backend.Fx.NetCore/Logging/FrameworkToBackendFxLoggerFactory.cs +++ b/src/abstractions/Backend.Fx/Logging/FrameworkToBackendFxLoggerFactory.cs @@ -1,22 +1,20 @@ using System.Diagnostics; -using Backend.Fx.Logging; +using Microsoft.Extensions.Logging; -namespace Backend.Fx.NetCore.Logging +namespace Backend.Fx.Logging { [DebuggerStepThrough] public class FrameworkToBackendFxLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory { public void Dispose() - { - } + { } public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { return new FrameworkToBackendFxLogger(LogManager.Create(categoryName)); } - public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) - { - } + public void AddProvider(ILoggerProvider provider) + { } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs b/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs index d36959c7..ddb23609 100644 --- a/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs @@ -8,6 +8,7 @@ public interface IExceptionLogger void LogException(Exception exception); } + public class ExceptionLogger : IExceptionLogger { private readonly ILogger _logger; @@ -29,4 +30,4 @@ public void LogException(Exception exception) } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/ILogger.cs b/src/abstractions/Backend.Fx/Logging/ILogger.cs index f7e5c24b..ecbfecfd 100644 --- a/src/abstractions/Backend.Fx/Logging/ILogger.cs +++ b/src/abstractions/Backend.Fx/Logging/ILogger.cs @@ -1,4 +1,5 @@ // ReSharper disable UnusedMethodReturnValue.Global + using System; using JetBrains.Annotations; @@ -94,4 +95,4 @@ public interface ILogger #endregion } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Logging/ILoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/ILoggerFactory.cs index 2aa76862..4949d9d6 100644 --- a/src/abstractions/Backend.Fx/Logging/ILoggerFactory.cs +++ b/src/abstractions/Backend.Fx/Logging/ILoggerFactory.cs @@ -7,7 +7,7 @@ public interface ILoggerFactory ILogger Create(string s); ILogger Create(Type t); ILogger Create(); - + void BeginActivity(int activityIndex); void Shutdown(); } diff --git a/src/abstractions/Backend.Fx/Logging/LogManager.cs b/src/abstractions/Backend.Fx/Logging/LogManager.cs index 8319f65a..c897484b 100644 --- a/src/abstractions/Backend.Fx/Logging/LogManager.cs +++ b/src/abstractions/Backend.Fx/Logging/LogManager.cs @@ -41,4 +41,4 @@ public static void Shutdown() _factory.Shutdown(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs b/src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs index 759cea10..40ac92f8 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs @@ -6,40 +6,46 @@ namespace Backend.Fx.Patterns.Authorization { - public abstract class AggregateAuthorization : IAggregateAuthorization where TAggregateRoot : AggregateRoot + public abstract class AggregateAuthorization : IAggregateAuthorization + where TAggregateRoot : AggregateRoot { private static readonly ILogger Logger = LogManager.Create>(); - - /// > + + /// + /// > public abstract Expression> HasAccessExpression { get; } - /// > + /// + /// > public virtual IQueryable Filter(IQueryable queryable) { return queryable.Where(HasAccessExpression); } - /// > + /// + /// > public abstract bool CanCreate(TAggregateRoot t); /// /// Implement a guard that might disallow modifying an existing aggregate. - /// This overload is called directly before saving modification of an instance, so that you can use the instance's state for deciding. - /// This default implementation forwards to + /// This overload is called directly before saving modification of an instance, so that you can use the instance's state + /// for deciding. + /// This default implementation forwards to /// public virtual bool CanModify(TAggregateRoot t) { - var canCreate = CanCreate(t); - Logger.Trace($"CanCreate({t.DebuggerDisplay}): {canCreate}"); - return canCreate; + bool canModify = CanCreate(t); + Logger.Trace($"CanModify({t.DebuggerDisplay}): {canModify}"); + return canModify; } - /// > + /// + /// > public virtual bool CanDelete(TAggregateRoot t) { - var canModify = CanModify(t); - Logger.Trace($"CanModify({t.DebuggerDisplay}): {canModify}"); - return canModify; + bool canDelete = CanModify(t); + Logger.Trace($"CanDelete({t.DebuggerDisplay}): {canDelete}"); + return canDelete; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs b/src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs index 6b416d79..7526ee4e 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs +++ b/src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs @@ -16,4 +16,4 @@ public override bool CanCreate(TAggregateRoot t) return true; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs b/src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs index 3e99e961..e98032be 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs +++ b/src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs @@ -16,4 +16,4 @@ public override bool CanCreate(TAggregateRoot t) return false; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs b/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs index 0eb6773b..65dc338b 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs @@ -1,12 +1,14 @@ -using System.Linq; +using System; +using System.Linq; using System.Linq.Expressions; using Backend.Fx.BuildingBlocks; namespace Backend.Fx.Patterns.Authorization { /// - /// Implements permissions on aggregate level. The respective instance is applied when creating an , - /// so that the repository never allows reading or writing of an aggregate without permissions. + /// Implements permissions on aggregate level. The respective instance is applied when creating an + /// , + /// so that the repository never allows reading or writing of an aggregate without permissions. /// /// public interface IAggregateAuthorization where TAggregateRoot : AggregateRoot @@ -14,10 +16,11 @@ public interface IAggregateAuthorization where TAggregateRoot : /// /// Express a filter for repository queryable /// - Expression> HasAccessExpression { get; } + Expression> HasAccessExpression { get; } /// - /// Only if the filter expression is not sufficient, you can override this method to apply the filtering to the queryable directly. + /// Only if the filter expression is not sufficient, you can override this method to apply the filtering to the queryable + /// directly. /// IQueryable Filter(IQueryable queryable); @@ -29,14 +32,16 @@ public interface IAggregateAuthorization where TAggregateRoot : /// /// Implement a guard that might disallow modifying an existing aggregate. - /// This overload is called directly before saving modification of an instance, so that you can use the instance's state for deciding. + /// This overload is called directly before saving modification of an instance, so that you can use the instance's state + /// for deciding. /// bool CanModify(TAggregateRoot t); /// /// Implement a guard that might disallow deleting an existing aggregate. - /// This overload is called directly before saving modification of an instance, so that you can use the instance's state for deciding. + /// This overload is called directly before saving modification of an instance, so that you can use the instance's state + /// for deciding. /// bool CanDelete(TAggregateRoot t); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs index 27f0100d..98744fdf 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs @@ -6,9 +6,5 @@ namespace Backend.Fx.Patterns.DataGeneration /// Will appear on the message bus when the data generation process has been completed /// public class DataGenerated : IntegrationEvent - { - public DataGenerated(int tenantId) : base(tenantId) - { - } - } -} \ No newline at end of file + { } +} diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs index ec544dbc..0607fb68 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Backend.Fx.Environment.Authentication; @@ -13,6 +14,7 @@ public interface IDataGenerationContext void SeedDataForTenant(TenantId tenantId, bool isDemoTenant); } + public class DataGenerationContext : IDataGenerationContext { private static readonly ILogger Logger = LogManager.Create(); @@ -25,34 +27,37 @@ public DataGenerationContext(ICompositionRoot compositionRoot, IBackendFxApplica _compositionRoot = compositionRoot; _invoker = invoker; } - + public void SeedDataForTenant(TenantId tenantId, bool isDemoTenant) { using (Logger.InfoDuration($"Seeding data for tenant {tenantId.Value}")) { Type[] dataGeneratorTypesToRun = GetDataGeneratorTypes(_compositionRoot, isDemoTenant); - foreach (Type dataGeneratorTypeToRun in dataGeneratorTypesToRun) + foreach (var dataGeneratorTypeToRun in dataGeneratorTypesToRun) { - _invoker.Invoke(instanceProvider => - { - IDataGenerator dataGenerator = instanceProvider - .GetInstances() - .Single(dg => dg.GetType() == dataGeneratorTypeToRun); - dataGenerator.Generate(); - }, new SystemIdentity(), tenantId); + _invoker.Invoke( + instanceProvider => + { + var dataGenerator = instanceProvider + .GetInstances() + .Single(dg => dg.GetType() == dataGeneratorTypeToRun); + dataGenerator.Generate(); + }, + new SystemIdentity(), + tenantId); } } } private static Type[] GetDataGeneratorTypes(ICompositionRoot compositionRoot, bool includeDemoDataGenerators) { - using (IInjectionScope scope = compositionRoot.BeginScope()) + using (var scope = compositionRoot.BeginScope()) { - var dataGenerators = scope - .InstanceProvider - .GetInstances() - .OrderBy(dg => dg.Priority) - .Select(dg => dg.GetType()); + IEnumerable dataGenerators = scope + .InstanceProvider + .GetInstances() + .OrderBy(dg => dg.Priority) + .Select(dg => dg.GetType()); if (!includeDemoDataGenerators) { @@ -63,4 +68,4 @@ private static Type[] GetDataGeneratorTypes(ICompositionRoot compositionRoot, bo } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs index 9dfb95b0..abe5d404 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs @@ -1,7 +1,7 @@ -namespace Backend.Fx.Patterns.DataGeneration -{ - using Logging; +using Backend.Fx.Logging; +namespace Backend.Fx.Patterns.DataGeneration +{ public interface IDataGenerator { /// @@ -12,9 +12,10 @@ public interface IDataGenerator void Generate(); } + /// - /// Implement this abstract class and mark it either with the - /// or depending whether you want it to run in all environments + /// Implement this abstract class and mark it either with the + /// or depending whether you want it to run in all environments /// or only on development environments. /// Any implementation is automatically picked up by the injection container, so no extra plumbing is required. /// You can require any application or domain service including repositories via constructor parameter. @@ -53,10 +54,10 @@ public void Generate() protected abstract void Initialize(); /// - /// return true, if the generator should be executed. Generators must be implemented idempotent, + /// return true, if the generator should be executed. Generators must be implemented idempotent, /// since they're all executed on application start /// /// protected abstract bool ShouldRun(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/GenerateDataOnBoot.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/GenerateDataOnBoot.cs index 9cad23a1..86dfd41d 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/GenerateDataOnBoot.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/GenerateDataOnBoot.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; @@ -10,48 +9,43 @@ namespace Backend.Fx.Patterns.DataGeneration { /// - /// Enriches the by calling all data generators for all tenants on application start. + /// Enriches the by calling all data generators for all tenants on application start. /// public class GenerateDataOnBoot : IBackendFxApplication { private static readonly ILogger Logger = LogManager.Create(); - private readonly ITenantIdProvider _tenantIdProvider; private readonly IBackendFxApplication _application; - private readonly IModule _dataGenerationModule; private readonly ManualResetEventSlim _dataGenerated = new ManualResetEventSlim(false); - public IDataGenerationContext DataGenerationContext { get; [UsedImplicitly] private set; } + private readonly ITenantIdProvider _tenantIdProvider; - public GenerateDataOnBoot(ITenantIdProvider tenantIdProvider, IModule dataGenerationModule, IBackendFxApplication application) + public GenerateDataOnBoot(ITenantIdProvider tenantIdProvider, IBackendFxApplication application) { _tenantIdProvider = tenantIdProvider; _application = application; - _dataGenerationModule = dataGenerationModule; - DataGenerationContext = new DataGenerationContext(_application.CompositionRoot, - _application.Invoker); + DataGenerationContext = new DataGenerationContext( + _application.CompositionRoot, + _application.Invoker); } + public IDataGenerationContext DataGenerationContext { get; [UsedImplicitly] private set; } + public void Dispose() { _application.Dispose(); } public IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; + public ICompositionRoot CompositionRoot => _application.CompositionRoot; + public IBackendFxApplicationInvoker Invoker => _application.Invoker; public IMessageBus MessageBus => _application.MessageBus; - public bool WaitForBoot(int timeoutMilliSeconds = Int32.MaxValue, CancellationToken cancellationToken = default) - { - return _dataGenerated.Wait(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); public async Task BootAsync(CancellationToken cancellationToken = default) { - _application.CompositionRoot.RegisterModules(_dataGenerationModule); await _application.BootAsync(cancellationToken); - + SeedDataForAllActiveTenants(); _dataGenerated.Set(); @@ -61,20 +55,20 @@ private void SeedDataForAllActiveTenants() { using (Logger.InfoDuration("Seeding data")) { - var prodTenantIds = _tenantIdProvider.GetActiveProductionTenantIds(); - foreach (TenantId prodTenantId in prodTenantIds) + TenantId[] prodTenantIds = _tenantIdProvider.GetActiveProductionTenantIds(); + foreach (var prodTenantId in prodTenantIds) { DataGenerationContext.SeedDataForTenant(prodTenantId, false); - _application.MessageBus.Publish(new DataGenerated(prodTenantId.Value)); + _application.MessageBus.Publish(new DataGenerated()); } - var demoTenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds(); - foreach (TenantId demoTenantId in demoTenantIds) + TenantId[] demoTenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds(); + foreach (var demoTenantId in demoTenantIds) { DataGenerationContext.SeedDataForTenant(demoTenantId, true); - _application.MessageBus.Publish(new DataGenerated(demoTenantId.Value)); + _application.MessageBus.Publish(new DataGenerated()); } } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs index 6908aba5..ac2cc648 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs @@ -1,9 +1,8 @@ namespace Backend.Fx.Patterns.DataGeneration { /// - /// Marks an as active in development environments only + /// Marks an as active in development environments only /// public interface IDemoDataGenerator : IDataGenerator - { - } -} \ No newline at end of file + { } +} diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs index 17733651..ae811897 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs @@ -1,9 +1,8 @@ namespace Backend.Fx.Patterns.DataGeneration { /// - /// Marks an as active in all environments + /// Marks an as active in all environments /// public interface IProductiveDataGenerator : IDataGenerator - { - } -} \ No newline at end of file + { } +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs index b32860e4..8343216c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs @@ -1,12 +1,7 @@ using System; -using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.EventAggregation.Integration; namespace Backend.Fx.Patterns.DependencyInjection @@ -17,7 +12,7 @@ namespace Backend.Fx.Patterns.DependencyInjection public interface IBackendFxApplication : IDisposable { /// - /// The async invoker runs a given action asynchronously in an application scope with injection facilities + /// The async invoker runs a given action asynchronously in an application scope with injection facilities /// IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } @@ -27,7 +22,7 @@ public interface IBackendFxApplication : IDisposable ICompositionRoot CompositionRoot { get; } /// - /// The invoker runs a given action in an application scope with injection facilities + /// The invoker runs a given action in an application scope with injection facilities /// IBackendFxApplicationInvoker Invoker { get; } @@ -36,14 +31,6 @@ public interface IBackendFxApplication : IDisposable /// IMessageBus MessageBus { get; } - /// - /// allows synchronously awaiting application startup - /// - bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default); - - [Obsolete("Use BootAsync()")] - Task Boot(CancellationToken cancellationToken = default); - /// /// Initializes and starts the application (async) /// @@ -55,32 +42,28 @@ public interface IBackendFxApplication : IDisposable public class BackendFxApplication : IBackendFxApplication { private static readonly ILogger Logger = LogManager.Create(); - private readonly ManualResetEventSlim _isBooted = new ManualResetEventSlim(false); - + /// /// Initializes the application's runtime instance /// /// The composition root of the dependency injection framework /// The message bus implementation used by this application instance /// - public BackendFxApplication(ICompositionRoot compositionRoot, IMessageBus messageBus, IExceptionLogger exceptionLogger) + public BackendFxApplication( + ICompositionRoot compositionRoot, + IMessageBus messageBus, + IExceptionLogger exceptionLogger) { var invoker = new BackendFxApplicationInvoker(compositionRoot); AsyncInvoker = new ExceptionLoggingAsyncInvoker(exceptionLogger, invoker); Invoker = new ExceptionLoggingInvoker(exceptionLogger, invoker); MessageBus = messageBus; - MessageBus.ProvideInvoker(new SequentializingBackendFxApplicationInvoker( - new WaitForBootInvoker(this, - new ExceptionLoggingAndHandlingInvoker(exceptionLogger, Invoker)))); + MessageBus.ProvideInvoker( + new SequentializingBackendFxApplicationInvoker( + new WaitForBootInvoker( + this, + new ExceptionLoggingAndHandlingInvoker(exceptionLogger, Invoker)))); CompositionRoot = compositionRoot; - CompositionRoot.InfrastructureModule.RegisterScoped(); - CompositionRoot.InfrastructureModule.RegisterScoped, CurrentCorrelationHolder>(); - CompositionRoot.InfrastructureModule.RegisterScoped, CurrentIdentityHolder>(); - CompositionRoot.InfrastructureModule.RegisterScoped, CurrentTenantIdHolder>(); - CompositionRoot.InfrastructureModule.RegisterScoped(); - CompositionRoot.InfrastructureModule.RegisterScoped(() => new DomainEventAggregator(compositionRoot)); - CompositionRoot.InfrastructureModule.RegisterScoped( - () => new MessageBusScope(MessageBus, compositionRoot.InstanceProvider.GetInstance>())); } public IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } @@ -91,20 +74,18 @@ public BackendFxApplication(ICompositionRoot compositionRoot, IMessageBus messag public IMessageBus MessageBus { get; } - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); - public Task BootAsync(CancellationToken cancellationToken = default) { Logger.Info("Booting application"); CompositionRoot.Verify(); MessageBus.Connect(); - _isBooted.Set(); return Task.CompletedTask; } - public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default) + public void Dispose() { - return _isBooted.Wait(timeoutMilliSeconds, cancellationToken); + Dispose(true); + GC.SuppressFinalize(this); } protected void Dispose(bool disposing) @@ -115,11 +96,5 @@ protected void Dispose(bool disposing) CompositionRoot?.Dispose(); } } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs index f96e6da0..73fe6537 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs @@ -16,7 +16,11 @@ public interface IBackendFxApplicationAsyncInvoker /// The acting identity /// The targeted tenant id /// The correlation id, when it was continued - Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null); + Task InvokeAsync( + Func awaitableAsyncAction, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null); } @@ -26,25 +30,32 @@ public interface IBackendFxApplicationInvoker /// The acting identity /// The targeted tenant id /// The correlation id, when it was continued - void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null); + void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null); } public class BackendFxApplicationInvoker : IBackendFxApplicationInvoker, IBackendFxApplicationAsyncInvoker { - private readonly ICompositionRoot _compositionRoot; private static readonly ILogger Logger = LogManager.Create(); + private readonly ICompositionRoot _compositionRoot; public BackendFxApplicationInvoker(ICompositionRoot compositionRoot) { _compositionRoot = compositionRoot; } - - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public async Task InvokeAsync( + Func awaitableAsyncAction, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { - Logger.Info($"Invoking synchronous action as {identity.Name} in {tenantId}"); - using (IInjectionScope injectionScope = BeginScope(identity, tenantId, correlationId)) + Logger.Info($"Invoking asynchronous action as {identity.Name} in {tenantId}"); + using (var injectionScope = BeginScope(identity, tenantId, correlationId)) { using (UseDurationLogger(injectionScope)) { @@ -52,7 +63,7 @@ public void Invoke(Action action, IIdentity identity, TenantI try { operation.Begin(); - action.Invoke(injectionScope.InstanceProvider); + await awaitableAsyncAction.Invoke(injectionScope.InstanceProvider); injectionScope.InstanceProvider.GetInstance().RaiseEvents(); operation.Complete(); } @@ -62,16 +73,19 @@ public void Invoke(Action action, IIdentity identity, TenantI throw; } - var messageBusScope = injectionScope.InstanceProvider.GetInstance(); - AsyncHelper.RunSync(() => messageBusScope.RaiseEvents()); + await injectionScope.InstanceProvider.GetInstance().RaiseEvents(); } } } - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { - Logger.Info($"Invoking asynchronous action as {identity.Name} in {tenantId}"); - using (IInjectionScope injectionScope = BeginScope(identity, tenantId, correlationId)) + Logger.Info($"Invoking synchronous action as {identity.Name} in {tenantId}"); + using (var injectionScope = BeginScope(identity, tenantId, correlationId)) { using (UseDurationLogger(injectionScope)) { @@ -79,7 +93,7 @@ public async Task InvokeAsync(Func awaitableAsyncAction try { operation.Begin(); - await awaitableAsyncAction.Invoke(injectionScope.InstanceProvider); + action.Invoke(injectionScope.InstanceProvider); injectionScope.InstanceProvider.GetInstance().RaiseEvents(); operation.Complete(); } @@ -89,15 +103,15 @@ public async Task InvokeAsync(Func awaitableAsyncAction throw; } - await injectionScope.InstanceProvider.GetInstance().RaiseEvents(); + var messageBusScope = injectionScope.InstanceProvider.GetInstance(); + AsyncHelper.RunSync(() => messageBusScope.RaiseEvents()); } } } - private IInjectionScope BeginScope(IIdentity identity, TenantId tenantId, Guid? correlationId) { - IInjectionScope injectionScope = _compositionRoot.BeginScope(); + var injectionScope = _compositionRoot.BeginScope(); tenantId = tenantId ?? new TenantId(null); injectionScope.InstanceProvider.GetInstance>().ReplaceCurrent(tenantId); @@ -106,21 +120,21 @@ private IInjectionScope BeginScope(IIdentity identity, TenantId tenantId, Guid? if (correlationId.HasValue) { - injectionScope.InstanceProvider.GetInstance>().Current.Resume(correlationId.Value); + injectionScope.InstanceProvider.GetInstance>() + .Current.Resume(correlationId.Value); } return injectionScope; } - private static IDisposable UseDurationLogger(IInjectionScope injectionScope) { - IIdentity identity = injectionScope.InstanceProvider.GetInstance>().Current; - TenantId tenantId = injectionScope.InstanceProvider.GetInstance>().Current; - Correlation correlation = injectionScope.InstanceProvider.GetInstance>().Current; + var identity = injectionScope.InstanceProvider.GetInstance>().Current; + var tenantId = injectionScope.InstanceProvider.GetInstance>().Current; + var correlation = injectionScope.InstanceProvider.GetInstance>().Current; return Logger.InfoDuration( $"Starting scope {injectionScope.SequenceNumber} (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}", $"Ended scope {injectionScope.SequenceNumber} (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}"); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs index f278b237..4d9abf4c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs @@ -4,7 +4,8 @@ namespace Backend.Fx.Patterns.DependencyInjection { /// - /// A guid that is unique for an invocation. In case of an invocation as result of handling an integration event, the correlation + /// A guid that is unique for an invocation. In case of an invocation as result of handling an integration event, the + /// correlation /// is stable, that is, the correlation can be used to track a logical action over different systems. /// public class Correlation @@ -19,4 +20,4 @@ public void Resume(Guid correlationId) Id = correlationId; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs index 70a9f67e..d3174ac3 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs @@ -12,4 +12,4 @@ protected override string Describe(Correlation instance) return $"Correlation: {instance?.Id.ToString() ?? "NULL"}"; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs index e572e178..3ea105af 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs @@ -15,6 +15,7 @@ public interface ICurrentTHolder where T : class T ProvideInstance(); } + public abstract class CurrentTHolder : ICurrentTHolder where T : class { private static readonly ILogger Logger = LogManager.Create>(); @@ -27,7 +28,7 @@ protected CurrentTHolder(T initial) { _current = initial; } - + public T Current { get @@ -45,9 +46,13 @@ public T Current public void ReplaceCurrent(T newCurrentInstance) { - if (Equals(_current, newCurrentInstance)) return; + if (Equals(_current, newCurrentInstance)) + { + return; + } - Logger.Debug($"Replacing current instance of {typeof(T).Name} ({Describe(Current)}) with another instance ({Describe(newCurrentInstance)})"); + Logger.Debug( + $"Replacing current instance of {typeof(T).Name} ({Describe(Current)}) with another instance ({Describe(newCurrentInstance)})"); _current = newCurrentInstance; } @@ -55,4 +60,4 @@ public void ReplaceCurrent(T newCurrentInstance) protected abstract string Describe(T instance); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs index 2616f0aa..3e07f17f 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs @@ -10,13 +10,19 @@ public class ExceptionLoggingAndHandlingInvoker : IBackendFxApplicationInvoker private readonly IExceptionLogger _exceptionLogger; private readonly IBackendFxApplicationInvoker _invoker; - public ExceptionLoggingAndHandlingInvoker(IExceptionLogger exceptionLogger, IBackendFxApplicationInvoker invoker) + public ExceptionLoggingAndHandlingInvoker( + IExceptionLogger exceptionLogger, + IBackendFxApplicationInvoker invoker) { _exceptionLogger = exceptionLogger; _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { try { @@ -28,4 +34,4 @@ public void Invoke(Action action, IIdentity identity, TenantI } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs index f87ede34..542c8337 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs @@ -17,7 +17,11 @@ public ExceptionLoggingAsyncInvoker(IExceptionLogger exceptionLogger, IBackendFx _invoker = invoker; } - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public async Task InvokeAsync( + Func awaitableAsyncAction, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { try { @@ -30,4 +34,4 @@ public async Task InvokeAsync(Func awaitableAsyncAction } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs index 04097ea4..b9d39a7d 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs @@ -16,7 +16,11 @@ public ExceptionLoggingInvoker(IExceptionLogger exceptionLogger, IBackendFxAppli _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { try { @@ -29,4 +33,4 @@ public void Invoke(Action action, IIdentity identity, TenantI } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs index 2dcfa214..a0d8933a 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs @@ -1,5 +1,4 @@ using System; -using Backend.Fx.Patterns.EventAggregation.Domain; namespace Backend.Fx.Patterns.DependencyInjection { @@ -9,22 +8,15 @@ namespace Backend.Fx.Patterns.DependencyInjection /// the domain or application logic, this would result in the Service Locator anti pattern, described here: /// http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ /// - public interface ICompositionRoot : IDisposable, IDomainEventHandlerProvider + public interface ICompositionRoot : IDisposable { - void Verify(); - - void RegisterModules(params IModule[] modules); - - IInjectionScope BeginScope(); - /// /// Access to the container's resolution functionality /// IInstanceProvider InstanceProvider { get; } - - /// - /// Access to the container's configuration functionality - /// - IInfrastructureModule InfrastructureModule { get; } + + void Verify(); + + IInjectionScope BeginScope(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs index f5af5265..b0f77a3e 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs @@ -34,4 +34,4 @@ public interface IInstanceProvider /// IEnumerable GetInstances() where T : class; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs deleted file mode 100644 index 762f2042..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Backend.Fx.Patterns.DependencyInjection -{ - /// - /// A logically cohesive bunch of services - /// - public interface IModule - { - void Register(ICompositionRoot compositionRoot); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs deleted file mode 100644 index 8dc57a87..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Reflection; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - public interface IInfrastructureModule - { - void RegisterScoped() - where TImpl : class, TService - where TService : class; - - void RegisterScoped(Func factory) - where TService : class; - - void RegisterScoped(Type serviceType, Type implementationType); - void RegisterScoped(Type serviceType, Assembly[] assembliesToScan); - - void RegisterDecorator() where TService : class where TImpl : class, TService; - - void RegisterSingleton() where TService : class where TImpl : class, TService; - void RegisterInstance(TService instance) where TService : class; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs index 0cf52b1c..e1e973cb 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs @@ -27,4 +27,4 @@ protected InjectionScope(int sequenceNumber) public abstract void Dispose(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs index 52289819..d24fa7ee 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs @@ -4,19 +4,22 @@ namespace Backend.Fx.Patterns.DependencyInjection { /// - /// The basic interface of an operation invoked by the (or its async counterpart). - /// Decorate this interface to provide operation specific infrastructure services (like a database connection, a database transaction + /// The basic interface of an operation invoked by the (or its async + /// counterpart). + /// Decorate this interface to provide operation specific infrastructure services (like a database connection, a database + /// transaction /// an entry-exit logging etc.) /// public interface IOperation { void Begin(); - + void Complete(); void Cancel(); } + public class Operation : IOperation { private static readonly ILogger Logger = LogManager.Create(); @@ -29,10 +32,13 @@ public virtual void Begin() { if (_isActive != null) { - throw new InvalidOperationException($"Cannot begin an operation that is {(_isActive.Value ? "active" : "terminated")}"); + throw new InvalidOperationException( + $"Cannot begin an operation that is {(_isActive.Value ? "active" : "terminated")}"); } - _lifetimeLogger = Logger.DebugDuration($"Beginning operation #{_instanceId}", $"Terminating operation #{_instanceId}"); + _lifetimeLogger = Logger.DebugDuration( + $"Beginning operation #{_instanceId}", + $"Terminating operation #{_instanceId}"); _isActive = true; } @@ -41,9 +47,10 @@ public virtual void Complete() Logger.Info($"Completing operation #{_instanceId}."); if (_isActive != true) { - throw new InvalidOperationException($"Cannot begin an operation that is {(_isActive == false ? "terminated" : "not active")}"); + throw new InvalidOperationException( + $"Cannot begin an operation that is {(_isActive == false ? "terminated" : "not active")}"); } - + _isActive = false; _lifetimeLogger?.Dispose(); _lifetimeLogger = null; @@ -54,11 +61,13 @@ public void Cancel() Logger.Info($"Canceling operation #{_instanceId}."); if (_isActive != true) { - throw new InvalidOperationException($"Cannot cancel an operation that is {(_isActive == false ? "terminated" : "not active")}"); + throw new InvalidOperationException( + $"Cannot cancel an operation that is {(_isActive == false ? "terminated" : "not active")}"); } + _isActive = false; _lifetimeLogger?.Dispose(); _lifetimeLogger = null; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Pure/ScopedServices.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Pure/ScopedServices.cs new file mode 100644 index 00000000..094cb7a3 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Pure/ScopedServices.cs @@ -0,0 +1,192 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.RandomData; + +namespace Backend.Fx.Patterns.DependencyInjection.Pure +{ + /// + /// A strongly typed registry of instances available during a scope, without the need for a dependency injection container. + /// + /// + /// This pattern is called "Pure DI", see https://blog.ploeh.dk/2012/11/06/WhentouseaDIContainer/ for details. + /// It is very useful in testing scenarios. + /// + public interface IScopedServices : IDisposable + { + IPersistenceSession PersistenceSession { get; } + + AdjustableClock Clock { get; } + + IDomainEventAggregator EventAggregator { get; } + + IMessageBusScope MessageBusScope { get; } + + ICurrentTHolder IdentityHolder { get; } + + ICurrentTHolder TenantIdHolder { get; } + + ICurrentTHolder CorrelationHolder { get; } + + TenantId TenantId { get; } + } + + + public abstract class ScopedServices : IScopedServices + { + private readonly Assembly[] _domainAssemblies; + private readonly Lazy _persistenceSession; + private bool _doAutoFlush = true; + + protected ScopedServices( + IClock clock, + IIdentity identity, + TenantId tenantId, + params Assembly[] domainAssemblies) + { + _domainAssemblies = domainAssemblies; + Clock = new AdjustableClock(new FrozenClock(clock)); + TenantIdHolder = CurrentTenantIdHolder.Create(tenantId); + IdentityHolder = CurrentIdentityHolder.Create(identity); + CorrelationHolder = new CurrentCorrelationHolder(); + _persistenceSession = new Lazy(() => CreatePersistenceSession()); + } + + public ICanFlush CanFlush => _persistenceSession.Value; + + public IPersistenceSession PersistenceSession => _persistenceSession.Value; + + public ICurrentTHolder TenantIdHolder { get; } + + public ICurrentTHolder CorrelationHolder { get; } + + public ICurrentTHolder IdentityHolder { get; } + + public AdjustableClock Clock { get; } + + public abstract IDomainEventAggregator EventAggregator { get; } + + public abstract IMessageBusScope MessageBusScope { get; } + + public TenantId TenantId => TenantIdHolder.Current; + + public void Dispose() + { + if (_doAutoFlush) + { + Flush(); + } + + Dispose(true); + GC.SuppressFinalize(this); + } + + public ScopedServices OptOutOfAutoFlush() + { + _doAutoFlush = false; + return this; + } + + public T AddToRepository(T aggregateRoot) where T : AggregateRoot + { + GetRepository().Add(aggregateRoot); + Flush(); + return aggregateRoot; + } + + public T Get(int id) where T : AggregateRoot + { + return GetRepository().Single(id); + } + + public abstract IRepository GetRepository() + where TAggregateRoot : AggregateRoot; + + public abstract IAsyncRepository GetAsyncRepository() + where TAggregateRoot : AggregateRoot; + + public T GetRandom() where T : AggregateRoot + { + return GetRepository().AggregateQueryable.Random(); + } + + public void Flush() + { + PersistenceSession.Flush(); + } + + public void Complete() + { + Flush(); + } + + protected object GetAggregateAuthorization(ICurrentTHolder identityHolder, Type aggregateRootType) + { + var aggregateDefinitionType = _domainAssemblies + .SelectMany(ass => ass.GetTypes()) + .Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract) + .SingleOrDefault( + t => + typeof(IAggregateAuthorization<>).MakeGenericType(aggregateRootType) + .GetTypeInfo() + .IsAssignableFrom(t.GetTypeInfo())); + if (aggregateDefinitionType == null) + { + throw new InvalidOperationException($"No Aggregate authorization for {aggregateRootType.Name} found"); + } + + ParameterInfo[] constructorParameterTypes + = aggregateDefinitionType.GetConstructors().Single().GetParameters(); + var constructorParameters = new object[constructorParameterTypes.Length]; + for (var i = 0; i < constructorParameterTypes.Length; i++) + { + if (constructorParameterTypes[i].ParameterType == typeof(IIdentity)) + { + constructorParameters[i] = identityHolder.Current; + continue; + } + + if (constructorParameterTypes[i].ParameterType == typeof(ICurrentTHolder)) + { + constructorParameters[i] = identityHolder; + continue; + } + + if (constructorParameterTypes[i].ParameterType == typeof(IClock)) + { + constructorParameters[i] = Clock; + continue; + } + + constructorParameters[i] = ProvideInstance(constructorParameterTypes[i].ParameterType); + + if (constructorParameters[i] == null) + { + throw new InvalidOperationException( + $"No implementation for {constructorParameterTypes[i].ParameterType.Name} provided"); + } + } + + return Activator.CreateInstance(aggregateDefinitionType, constructorParameters); + } + + protected virtual ParameterInfo ProvideInstance(Type parameterType) + { + return null; + } + + protected virtual void Dispose(bool disposing) + { } + + protected abstract IPersistenceSession CreatePersistenceSession(); + } +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Pure/SingletonServices.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Pure/SingletonServices.cs new file mode 100644 index 00000000..fbf0c2a8 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Pure/SingletonServices.cs @@ -0,0 +1,50 @@ +using System; +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.Patterns.DependencyInjection.Pure +{ + /// + /// A strongly typed registry of singleton instances, without the need for a dependency injection container. + /// + /// + /// This pattern is called "Pure DI", see https://blog.ploeh.dk/2012/11/06/WhentouseaDIContainer/ for details. + /// It is very useful in testing scenarios. + /// + public interface ISingletonServices + { + AdjustableClock Clock { get; } + + IEntityIdGenerator EntityIdGenerator { get; } + + Assembly[] Assemblies { get; } + } + + + public interface ISingletonServices : ISingletonServices + where TScopedServices : IScopedServices + { + TScopedServices BeginScope(TenantId tenantId, IIdentity identity = null); + } + + + public abstract class SingletonServices : ISingletonServices + where TScopedServices : IScopedServices + { + protected SingletonServices(params Assembly[] assemblies) + { + Assemblies = assemblies ?? Array.Empty(); + } + + public Assembly[] Assemblies { get; } + + public virtual AdjustableClock Clock { get; } = new AdjustableClock(new WallClock()); + + public abstract IEntityIdGenerator EntityIdGenerator { get; } + + public abstract TScopedServices BeginScope(TenantId tenantId, IIdentity identity = null); + } +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs index 893aac1b..b760ec5c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs @@ -5,20 +5,24 @@ namespace Backend.Fx.Patterns.DependencyInjection { /// - /// Decorates the to prevent parallel invocation. + /// Decorates the to prevent parallel invocation. /// public class SequentializingBackendFxApplicationInvoker : IBackendFxApplicationInvoker { - private readonly object _syncLock = new object(); private readonly IBackendFxApplicationInvoker _backendFxApplicationInvokerImplementation; + private readonly object _syncLock = new object(); - public SequentializingBackendFxApplicationInvoker(IBackendFxApplicationInvoker backendFxApplicationInvokerImplementation) + public SequentializingBackendFxApplicationInvoker( + IBackendFxApplicationInvoker backendFxApplicationInvokerImplementation) { _backendFxApplicationInvokerImplementation = backendFxApplicationInvokerImplementation; } - - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { lock (_syncLock) { @@ -26,4 +30,4 @@ public void Invoke(Action action, IIdentity identity, TenantI } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs index 3dcecb9f..8a25e6ac 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs @@ -15,10 +15,13 @@ public WaitForBootInvoker(IBackendFxApplication application, IBackendFxApplicati _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) { - _application.WaitForBoot(); _invoker.Invoke(action, identity, tenantId, correlationId); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs index a196c478..63352d9b 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs @@ -6,20 +6,6 @@ namespace Backend.Fx.Patterns.EventAggregation.Domain { public class DomainEventAggregator : IDomainEventAggregator { - private class HandleAction - { - public HandleAction(string domainEventName, string handlerTypeName, Action action) - { - DomainEventName = domainEventName; - HandlerTypeName = handlerTypeName; - Action = action; - } - - public string DomainEventName { get; } - public string HandlerTypeName { get; } - public Action Action { get; } - } - private static readonly ILogger Logger = LogManager.Create(); private readonly IDomainEventHandlerProvider _domainEventHandlerProvider; private readonly ConcurrentQueue _handleActions = new ConcurrentQueue(); @@ -37,7 +23,8 @@ public DomainEventAggregator(IDomainEventHandlerProvider domainEventHandlerProvi /// public void PublishDomainEvent(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent { - foreach (var injectedHandler in _domainEventHandlerProvider.GetAllEventHandlers()) + foreach (IDomainEventHandler injectedHandler in _domainEventHandlerProvider + .GetAllEventHandlers()) { var handleAction = new HandleAction( typeof(TDomainEvent).Name, @@ -45,13 +32,14 @@ public void PublishDomainEvent(TDomainEvent domainEvent) where TDo () => injectedHandler.Handle(domainEvent)); _handleActions.Enqueue(handleAction); - Logger.Debug($"Invocation of {injectedHandler.GetType().Name} for domain event {typeof(TDomainEvent).Name} registered. It will be executed on completion of operation"); + Logger.Debug( + $"Invocation of {injectedHandler.GetType().Name} for domain event {typeof(TDomainEvent).Name} registered. It will be executed on completion of operation"); } } public void RaiseEvents() { - while (_handleActions.TryDequeue(out HandleAction handleAction)) + while (_handleActions.TryDequeue(out var handleAction)) { try { @@ -59,10 +47,29 @@ public void RaiseEvents() } catch (Exception ex) { - Logger.Error(ex, $"Handling of {handleAction.DomainEventName} by {handleAction.HandlerTypeName} failed."); + Logger.Error( + ex, + $"Handling of {handleAction.DomainEventName} by {handleAction.HandlerTypeName} failed."); throw; } } } + + + private class HandleAction + { + public HandleAction(string domainEventName, string handlerTypeName, Action action) + { + DomainEventName = domainEventName; + HandlerTypeName = handlerTypeName; + Action = action; + } + + public string DomainEventName { get; } + + public string HandlerTypeName { get; } + + public Action Action { get; } + } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs index 8f9af72b..4f3e81e3 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs @@ -5,6 +5,5 @@ /// Handlers are called through dependency injection /// public interface IDomainEvent - { - } -} \ No newline at end of file + { } +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs index 42891ec5..43dd1c12 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs @@ -9,4 +9,4 @@ public interface IDomainEventAggregator void PublishDomainEvent(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent; void RaiseEvents(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs index eb5b4a24..ee4d78a5 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs @@ -4,4 +4,4 @@ public interface IDomainEventHandler where TDomainEvent : IDoma { void Handle(TDomainEvent domainEvent); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs index c500524e..e4db5e9b 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs @@ -1,7 +1,7 @@ +using System.Collections.Generic; + namespace Backend.Fx.Patterns.EventAggregation.Domain { - using System.Collections.Generic; - public interface IDomainEventHandlerProvider { /// @@ -9,6 +9,7 @@ public interface IDomainEventHandlerProvider /// /// /// - IEnumerable> GetAllEventHandlers() where TDomainEvent : IDomainEvent; + IEnumerable> GetAllEventHandlers() + where TDomainEvent : IDomainEvent; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs index 043bec4e..53351c0b 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs @@ -17,4 +17,4 @@ public void Handle(TIntegrationEvent eventData) _handleAction.Invoke(eventData); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs index 800fa2a9..3fa9d219 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs @@ -21,13 +21,13 @@ public void Process(IInstanceProvider instanceProvider, EventProcessingContext c object handlerInstance = instanceProvider.GetInstance(_handlerType); using (Logger.InfoDuration($"Invoking subscribed handler {_handlerType.GetDetailedTypeName()}")) { - ((IIntegrationMessageHandler) handlerInstance).Handle(context.DynamicEvent); + ((IIntegrationMessageHandler)handlerInstance).Handle(context.DynamicEvent); } } public bool Matches(object handler) { - return (Type) handler == _handlerType; + return (Type)handler == _handlerType; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs index eb913e8f..4a354455 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs @@ -6,9 +6,11 @@ namespace Backend.Fx.Patterns.EventAggregation.Integration public abstract class EventProcessingContext { public abstract TenantId TenantId { get; } + public abstract dynamic DynamicEvent { get; } + public abstract Guid CorrelationId { get; } public abstract IIntegrationEvent GetTypedEvent(Type eventType); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs index 2c2a0e1c..541d129a 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs @@ -5,8 +5,9 @@ public interface IIntegrationMessageHandler void Handle(dynamic eventData); } + public interface IIntegrationMessageHandler where TEvent : IIntegrationEvent { void Handle(TEvent eventData); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs index bce92202..70ed0eca 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs @@ -16,8 +16,8 @@ public interface IMessageBus : IDisposable /// /// Directly publishes an event on the message bus without delay. - /// In most cases you want to publish an event when the cause is considered as safely done, e.g. when the - /// wrapping transaction is committed. Use to let the framework raise all events + /// In most cases you want to publish an event when the cause is considered as safely done, e.g. when the + /// wrapping transaction is committed. Use to let the framework raise all events /// after completing the operation. /// /// @@ -60,4 +60,4 @@ void Unsubscribe(IIntegrationMessageHandler handler) void ProvideInvoker(IBackendFxApplicationInvoker invoker); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs index cd7883a0..9205f0ab 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs @@ -7,9 +7,11 @@ public interface IMessageNameProvider { [NotNull] string GetMessageName(); + [NotNull] string GetMessageName(Type t); + [NotNull] string GetMessageName(IIntegrationEvent integrationEvent); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs index 91ca9a39..e5947097 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs @@ -7,4 +7,4 @@ public interface ISubscription void Process(IInstanceProvider instanceProvider, EventProcessingContext context); bool Matches(object handler); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs index 3718ea1c..081a96f4 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs @@ -1,9 +1,9 @@ -namespace Backend.Fx.Patterns.EventAggregation.Integration -{ - using System; - using System.Threading.Tasks; - using Environment.MultiTenancy; +using System; +using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; +namespace Backend.Fx.Patterns.EventAggregation.Integration +{ public class InMemoryMessageBus : MessageBus { private readonly InMemoryMessageBusChannel _channel; @@ -12,12 +12,12 @@ public InMemoryMessageBus() { _channel = new InMemoryMessageBusChannel(); } - + public InMemoryMessageBus(InMemoryMessageBusChannel channel) { _channel = channel; } - + public override void Connect() { _channel.MessageReceived += ChannelOnMessageReceived; @@ -31,28 +31,27 @@ protected override void Dispose(bool disposing) protected override Task PublishOnMessageBus(IIntegrationEvent integrationEvent) { _channel.Publish(integrationEvent); - + // the returning Task is about publishing the event, not processing! return Task.CompletedTask; } protected override void Subscribe(string messageName) - { - } + { } protected override void Unsubscribe(string messageName) - { - } + { } private void ChannelOnMessageReceived( - object sender, + object sender, InMemoryMessageBusChannel.MessageReceivedEventArgs eventArgs) { Process( - MessageNameProvider.GetMessageName(eventArgs.IntegrationEvent), + MessageNameProvider.GetMessageName(eventArgs.IntegrationEvent), new InMemoryProcessingContext(eventArgs.IntegrationEvent)); } + private class InMemoryProcessingContext : EventProcessingContext { private readonly IIntegrationEvent _integrationEvent; @@ -65,6 +64,7 @@ public InMemoryProcessingContext(IIntegrationEvent integrationEvent) public override TenantId TenantId => new TenantId(_integrationEvent.TenantId); public override dynamic DynamicEvent => _integrationEvent; + public override Guid CorrelationId => _integrationEvent.CorrelationId; public override IIntegrationEvent GetTypedEvent(Type eventType) @@ -73,4 +73,4 @@ public override IIntegrationEvent GetTypedEvent(Type eventType) } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs index 30d17f64..e3528678 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs @@ -1,14 +1,13 @@ -using System.Collections.Concurrent; +using System; +using System.Threading.Tasks; +using System.Collections.Concurrent; namespace Backend.Fx.Patterns.EventAggregation.Integration { - using System; - using System.Threading.Tasks; - public class InMemoryMessageBusChannel { private readonly ConcurrentBag _messageHandlingTasks = new ConcurrentBag(); - + internal event EventHandler MessageReceived; internal void Publish(IIntegrationEvent integrationEvent) @@ -24,10 +23,11 @@ public async Task FinishHandlingAllMessagesAsync() await messageHandlingTask; } } - + + internal class MessageReceivedEventArgs { public IIntegrationEvent IntegrationEvent { get; set; } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs index 91d6a721..60f822e4 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs @@ -5,33 +5,37 @@ namespace Backend.Fx.Patterns.EventAggregation.Integration public interface IIntegrationEvent { Guid Id { get; } + DateTime CreationDate { get; } - int TenantId { get; } - Guid CorrelationId { get; } + + int TenantId { get; set; } + + Guid CorrelationId { get; set; } } + /// /// Events that should be handled in a separate context. Might be persisted as well using an external message bus. - /// See https://blogs.msdn.microsoft.com/cesardelatorre/2017/02/07/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/ + /// See + /// https://blogs.msdn.microsoft.com/cesardelatorre/2017/02/07/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/ /// public abstract class IntegrationEvent : IIntegrationEvent { - public Guid Id { get; } = Guid.NewGuid(); - - public DateTime CreationDate { get; } = DateTime.UtcNow; - - public int TenantId { get; } - - public Guid CorrelationId { get; private set; } - - internal void SetCorrelationId(Guid correlationId) - { - CorrelationId = correlationId; - } + protected IntegrationEvent() + { } + [Obsolete("TenantId is injected automatically when publishing the event")] protected IntegrationEvent(int tenantId) { TenantId = tenantId; } + + public Guid Id { get; } = Guid.NewGuid(); + + public DateTime CreationDate { get; } = DateTime.UtcNow; + + public int TenantId { get; set; } + + public Guid CorrelationId { get; set; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs index 6e0967f2..10d57ade 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs @@ -16,20 +16,24 @@ public abstract class MessageBus : IMessageBus /// Holds the registered handlers. /// Each event type name (key) matches to various subscriptions /// - private readonly ConcurrentDictionary> _subscriptions = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _subscriptions + = new ConcurrentDictionary>(); private IBackendFxApplicationInvoker _invoker; public IMessageNameProvider MessageNameProvider { get; } = new DefaultMessageNameProvider(); + public abstract void Connect(); public void ProvideInvoker(IBackendFxApplicationInvoker invoker) { if (_invoker != null && !Equals(_invoker, invoker)) { - throw new InvalidOperationException("This message bus instance has been linked to an application instance invoker before. " + - "You cannot share the same message bus instance between multiple applications."); + throw new InvalidOperationException( + "This message bus instance has been linked to an application instance invoker before. " + + "You cannot share the same message bus instance between multiple applications."); } + _invoker = invoker; } @@ -40,37 +44,39 @@ public Task Publish(IIntegrationEvent integrationEvent) protected abstract Task PublishOnMessageBus(IIntegrationEvent integrationEvent); - /// public void Subscribe(string messageName) where THandler : IIntegrationMessageHandler { Logger.Info($"Subscribing to {messageName}"); EnsureInvoker(); var subscription = new DynamicSubscription(typeof(THandler)); - _subscriptions.AddOrUpdate(messageName, - s => new List {subscription}, - (s, list) => - { - list.Add(subscription); - return list; - }); + _subscriptions.AddOrUpdate( + messageName, + s => new List { subscription }, + (s, list) => + { + list.Add(subscription); + return list; + }); Subscribe(messageName); } /// - public void Subscribe() where THandler : IIntegrationMessageHandler where TEvent : IIntegrationEvent + public void Subscribe() where THandler : IIntegrationMessageHandler + where TEvent : IIntegrationEvent { string eventName = MessageNameProvider.GetMessageName(); Logger.Info($"Subscribing to {eventName}"); EnsureInvoker(); var subscription = new TypedSubscription(typeof(THandler), typeof(TEvent)); - _subscriptions.AddOrUpdate(eventName, - s => new List {subscription}, - (s, list) => - { - list.Add(subscription); - return list; - }); + _subscriptions.AddOrUpdate( + eventName, + s => new List { subscription }, + (s, list) => + { + list.Add(subscription); + return list; + }); Subscribe(eventName); } @@ -81,13 +87,14 @@ public void Subscribe(IIntegrationMessageHandler handler) Logger.Info($"Subscribing to {eventName}"); EnsureInvoker(); var subscription = new SingletonSubscription(handler); - _subscriptions.AddOrUpdate(eventName, - s => new List {subscription}, - (s, list) => - { - list.Add(subscription); - return list; - }); + _subscriptions.AddOrUpdate( + eventName, + s => new List { subscription }, + (s, list) => + { + list.Add(subscription); + return list; + }); Subscribe(eventName); } @@ -102,7 +109,8 @@ public void Unsubscribe(string messageName) where THandler : IIntegrat Unsubscribe(messageName); } - public void Unsubscribe() where THandler : IIntegrationMessageHandler where TEvent : IIntegrationEvent + public void Unsubscribe() where THandler : IIntegrationMessageHandler + where TEvent : IIntegrationEvent { string eventName = MessageNameProvider.GetMessageName(); Logger.Info($"Unsubscribing from {eventName}"); @@ -163,13 +171,13 @@ private void EnsureInvoker() { if (_invoker == null) { - throw new InvalidOperationException("Before using the message bus you have to provide the application invoker by calling ProvideInvoker()"); + throw new InvalidOperationException( + "Before using the message bus you have to provide the application invoker by calling ProvideInvoker()"); } } protected virtual void Dispose(bool disposing) - { - } + { } public void Dispose() { @@ -177,6 +185,7 @@ public void Dispose() GC.SuppressFinalize(this); } + private class DefaultMessageNameProvider : IMessageNameProvider { public string GetMessageName() @@ -196,4 +205,4 @@ public string GetMessageName(IIntegrationEvent integrationEvent) } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs index 70880bef..71bade5d 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs @@ -1,14 +1,14 @@ -using Backend.Fx.Patterns.DependencyInjection; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Patterns.DependencyInjection; namespace Backend.Fx.Patterns.EventAggregation.Integration { - using System.Collections.Concurrent; - using System.Threading.Tasks; - public interface IMessageBusScope { /// - /// Enqueue an event to be raised later. + /// Enqueue an event to be raised later. /// Intention is to let events bubble up after an operation has terminated /// /// @@ -17,30 +17,40 @@ public interface IMessageBusScope Task RaiseEvents(); } + public class MessageBusScope : IMessageBusScope { - private readonly ConcurrentQueue _integrationEvents = new ConcurrentQueue(); - private readonly IMessageBus _messageBus; private readonly ICurrentTHolder _correlationHolder; - public MessageBusScope(IMessageBus messageBus, ICurrentTHolder correlationHolder) + private readonly ConcurrentQueue _integrationEvents + = new ConcurrentQueue(); + + private readonly IMessageBus _messageBus; + private readonly ICurrentTHolder _tenantIdHolder; + + public MessageBusScope( + IMessageBus messageBus, + ICurrentTHolder correlationHolder, + ICurrentTHolder tenantIdHolder) { _messageBus = messageBus; _correlationHolder = correlationHolder; + _tenantIdHolder = tenantIdHolder; } void IMessageBusScope.Publish(IIntegrationEvent integrationEvent) { - ((IntegrationEvent) integrationEvent).SetCorrelationId(_correlationHolder.Current.Id); + integrationEvent.CorrelationId = _correlationHolder.Current.Id; + integrationEvent.TenantId = _tenantIdHolder.Current.Value; _integrationEvents.Enqueue(integrationEvent); } public async Task RaiseEvents() { - while (_integrationEvents.TryDequeue(out IIntegrationEvent integrationEvent)) + while (_integrationEvents.TryDequeue(out var integrationEvent)) { await _messageBus.Publish(integrationEvent); } } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs index 424a605e..2445ec3f 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs @@ -17,7 +17,7 @@ public void Process(IInstanceProvider instanceProvider, EventProcessingContext c { using (Logger.InfoDuration($"Invoking subscribed handler {_handler.GetType().Name}")) { - _handler.Handle((TEvent) context.GetTypedEvent(typeof(TEvent))); + _handler.Handle((TEvent)context.GetTypedEvent(typeof(TEvent))); } } @@ -26,4 +26,4 @@ public bool Matches(object handler) return _handler == handler; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs index b5b1d0b9..b465faf0 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs @@ -22,21 +22,23 @@ public TypedSubscription(Type handlerType, Type eventType) public void Process(IInstanceProvider instanceProvider, EventProcessingContext context) { IIntegrationEvent integrationEvent = context.GetTypedEvent(_eventType); - MethodInfo handleMethod = _handlerType.GetRuntimeMethod("Handle", new[] {_eventType}); - Debug.Assert(handleMethod != null, $"No method with signature `Handle({_eventType.Name} event)` found on {_handlerType.Name}"); + MethodInfo handleMethod = _handlerType.GetRuntimeMethod("Handle", new[] { _eventType }); + Debug.Assert( + handleMethod != null, + $"No method with signature `Handle({_eventType.Name} event)` found on {_handlerType.Name}"); Logger.Info($"Getting subscribed handler instance of type {_handlerType.Name}"); object handlerInstance = instanceProvider.GetInstance(_handlerType); using (Logger.InfoDuration($"Invoking subscribed handler {_handlerType.GetDetailedTypeName()}")) { - handleMethod.Invoke(handlerInstance, new object[] {integrationEvent}); + handleMethod.Invoke(handlerInstance, new object[] { integrationEvent }); } } public bool Matches(object handler) { - return (Type) handler == _handlerType; + return (Type)handler == _handlerType; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs index 8c2b76bd..4b520eef 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs @@ -6,24 +6,30 @@ namespace Backend.Fx.Patterns.IdGeneration public abstract class HiLoIdGenerator : IIdGenerator { private static readonly ILogger Logger = LogManager.Create(); - private int _highId = -1; - private int _lowId = -1; private static readonly object Mutex = new object(); private readonly bool _isTraceEnabled; + private int _highId = -1; + private int _lowId = -1; protected HiLoIdGenerator() { _isTraceEnabled = Logger.IsTraceEnabled(); } + protected abstract int BlockSize { get; } + public int NextId() { lock (Mutex) { EnsureValidLowAndHiId(); - var nextId = _lowId; + int nextId = _lowId; Interlocked.Increment(ref _lowId); - if (_isTraceEnabled) Logger.Trace("Providing id {0}", nextId); + if (_isTraceEnabled) + { + Logger.Trace("Providing id {0}", nextId); + } + return nextId; } } @@ -34,12 +40,10 @@ private void EnsureValidLowAndHiId() { // first fetch from sequence in life time _lowId = GetNextBlockStart(); - _highId = _lowId + BlockSize- 1; + _highId = _lowId + BlockSize - 1; } } protected abstract int GetNextBlockStart(); - - protected abstract int BlockSize { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs index 5c59c6d2..ba1063a4 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs @@ -4,4 +4,4 @@ public interface IIdGenerator { int NextId(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs index 02a4516b..aaa774ba 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs @@ -2,8 +2,9 @@ { public interface ISequence { + int Increment { get; } + void EnsureSequence(); int GetNextValue(); - int Increment { get; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs index ec4b7619..61a93a01 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs @@ -1,19 +1,19 @@ namespace Backend.Fx.Patterns.IdGeneration { - public abstract class SequenceHiLoIdGenerator : HiLoIdGenerator + public class SequenceHiLoIdGenerator : HiLoIdGenerator { private readonly ISequence _sequence; - protected SequenceHiLoIdGenerator(ISequence sequence) + public SequenceHiLoIdGenerator(ISequence sequence) { _sequence = sequence; } + protected override int BlockSize => _sequence.Increment; + protected override int GetNextBlockStart() { return _sequence.GetNextValue(); } - - protected override int BlockSize => _sequence.Increment; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs index 9d696ff3..a80737e7 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs @@ -1,10 +1,10 @@ namespace Backend.Fx.Patterns.IdGeneration { - public abstract class SequenceIdGenerator : IIdGenerator + public class SequenceIdGenerator : IIdGenerator { private readonly ISequence _sequence; - protected SequenceIdGenerator(ISequence sequence) + public SequenceIdGenerator(ISequence sequence) { _sequence = sequence; } @@ -14,4 +14,4 @@ public int NextId() return _sequence.GetNextValue(); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs b/src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs index 552e21eb..afbfbb91 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs +++ b/src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs @@ -7,4 +7,4 @@ public interface IJob { void Run(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/Generator.cs b/src/abstractions/Backend.Fx/RandomData/Generator.cs index 77595ae6..2ca0d303 100644 --- a/src/abstractions/Backend.Fx/RandomData/Generator.cs +++ b/src/abstractions/Backend.Fx/RandomData/Generator.cs @@ -11,7 +11,7 @@ public abstract class Generator : IEnumerable public IEnumerator GetEnumerator() { const int maxRetries = 1000; - int retries = 0; + var retries = 0; while (true) { T next; @@ -23,7 +23,8 @@ public IEnumerator GetEnumerator() if (retries++ > maxRetries) { - throw new Exception($"Tried {maxRetries} times to generate a unique {typeof(T).Name} but did not succeed, aborting now."); + throw new Exception( + $"Tried {maxRetries} times to generate a unique {typeof(T).Name} but did not succeed, aborting now."); } } while (_identicalPreventionMemory.Contains(hashCode)); @@ -40,4 +41,4 @@ IEnumerator IEnumerable.GetEnumerator() protected abstract T Next(); } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs b/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs index 996f4f02..eadd462d 100644 --- a/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs @@ -11,9 +11,13 @@ public static string Generate() protected override string Next() { - var generated = Numbers.LandLineNetworks.Random(); - while (generated.Length < TestRandom.Instance.Next(8, 11)) generated += Numbers.Ciphers.Random(); + string generated = Numbers.LandLineNetworks.Random(); + while (generated.Length < TestRandom.Instance.Next(8, 11)) + { + generated += Numbers.Ciphers.Random(); + } + return generated; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/Letters.cs b/src/abstractions/Backend.Fx/RandomData/Letters.cs index ee079429..a193419f 100644 --- a/src/abstractions/Backend.Fx/RandomData/Letters.cs +++ b/src/abstractions/Backend.Fx/RandomData/Letters.cs @@ -2,35 +2,52 @@ { public static class Letters { + // ReSharper disable StringLiteralTypo public static string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public static string LowerCase = "abcdefghijklmnopqrstuvwxyz"; + // ReSharper restore StringLiteralTypo public static string RandomUpperCase(int length) { var random = string.Empty; - for (var i = 0; i < length; i++) random += UpperCase.Random(); + for (var i = 0; i < length; i++) + { + random += UpperCase.Random(); + } + return random; } public static string RandomLowerCase(int length) { var random = string.Empty; - for (var i = 0; i < length; i++) random += LowerCase.Random(); + for (var i = 0; i < length; i++) + { + random += LowerCase.Random(); + } + return random; } public static string RandomNormalCase(int length) { var random = string.Empty; - for (var i = 0; i < length; i++) random += i == 0 ? UpperCase.Random() : LowerCase.Random(); + for (var i = 0; i < length; i++) + { + random += i == 0 ? UpperCase.Random() : LowerCase.Random(); + } + return random; } - + public static string RandomPassword(int length = 10, int numberCount = 2, int specialCharCount = 2) { + // ReSharper disable StringLiteralTypo const string letters = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"; const string numbers = "23456789"; const string specials = "§$%&#+*-<>"; + // ReSharper restore StringLiteralTypo var password = new char[length]; for (var i = 0; i < password.Length; i++) @@ -38,12 +55,12 @@ public static string RandomPassword(int length = 10, int numberCount = 2, int sp password[i] = letters.Random(); } - for (int i = 0; i < numberCount; i++) + for (var i = 0; i < numberCount; i++) { password[TestRandom.Next(length)] = numbers.Random(); } - - for (int i = 0; i < specialCharCount; i++) + + for (var i = 0; i < specialCharCount; i++) { password[TestRandom.Next(length)] = specials.Random(); } @@ -51,4 +68,4 @@ public static string RandomPassword(int length = 10, int numberCount = 2, int sp return new string(password); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs index 8fcce937..c2aaa9e1 100644 --- a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs +++ b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs @@ -17,15 +17,18 @@ public static class LinqExtensions /// public static IEnumerable Shuffle(this IEnumerable source) { - if (source == null) throw new ArgumentNullException(nameof(source)); + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } - var sourceAsArray = source as T[] ?? source.ToArray(); + T[] sourceAsArray = source as T[] ?? source.ToArray(); - var n = sourceAsArray.Length; + int n = sourceAsArray.Length; while (n > 1) { - var k = TestRandom.Instance.Next(n--); - T temp = sourceAsArray[n]; + int k = TestRandom.Instance.Next(n--); + var temp = sourceAsArray[n]; sourceAsArray[n] = sourceAsArray[k]; sourceAsArray[k] = temp; } @@ -35,41 +38,61 @@ public static IEnumerable Shuffle(this IEnumerable source) public static T Random(this IEnumerable source) { - if (source == null) throw new ArgumentNullException(nameof(source)); + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } // ReSharper disable once PossibleMultipleEnumeration - if (TryAsQueryable(source, out var sourceQueryable, out var count)) + if (TryAsQueryable(source, out IQueryable sourceQueryable, out int count)) { if (count == 0) + { throw new ArgumentException( - $"The enumerable of {typeof(T).Name} does not contain any items, therefore no random item can be returned.", nameof(source)); + $"The enumerable of {typeof(T).Name} does not contain any items, therefore no random item can be returned.", + nameof(source)); + } return sourceQueryable.Skip(TestRandom.Next(count - 1)).First(); } // ReSharper disable once PossibleMultipleEnumeration - var sourceArray = source as T[] ?? source.ToArray(); + T[] sourceArray = source as T[] ?? source.ToArray(); if (sourceArray.Length == 0) + { throw new ArgumentException( - $"The enumerable of {typeof(T).Name} does not contain any items, therefore no random item can be returned.", nameof(source)); + $"The enumerable of {typeof(T).Name} does not contain any items, therefore no random item can be returned.", + nameof(source)); + } + return sourceArray.ElementAt(TestRandom.Next(sourceArray.Length)); } public static T RandomOrDefault(this IEnumerable source) { - if (source == null) throw new ArgumentNullException(nameof(source)); + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } // ReSharper disable once PossibleMultipleEnumeration - if (TryAsQueryable(source, out var sourceQueryable, out var count)) + if (TryAsQueryable(source, out IQueryable sourceQueryable, out int count)) { - if (count == 0) return default; + if (count == 0) + { + return default; + } return sourceQueryable.Skip(TestRandom.Next(count - 1)).FirstOrDefault(); } // ReSharper disable once PossibleMultipleEnumeration - var sourceArray = source as T[] ?? source.ToArray(); - if (sourceArray.Length == 0) return default; + T[] sourceArray = source as T[] ?? source.ToArray(); + if (sourceArray.Length == 0) + { + return default; + } + return sourceArray.ElementAt(TestRandom.Next(sourceArray.Length)); } @@ -87,8 +110,13 @@ private static bool TryAsQueryable(this IEnumerable source, out IQueryable return true; } - PropertyInfo idProperty = typeof(T).GetProperty(nameof(Identified.Id), BindingFlags.Instance | BindingFlags.Public); - if (idProperty != null) sourceQueryable = sourceQueryable.OrderBy(nameof(Identified.Id)); + var idProperty = typeof(T).GetProperty( + nameof(Identified.Id), + BindingFlags.Instance | BindingFlags.Public); + if (idProperty != null) + { + sourceQueryable = sourceQueryable.OrderBy(nameof(Identified.Id)); + } outQueryable = sourceQueryable; return true; @@ -97,4 +125,4 @@ private static bool TryAsQueryable(this IEnumerable source, out IQueryable return false; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs b/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs index f4546be8..0697a380 100644 --- a/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs +++ b/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs @@ -4,17 +4,26 @@ namespace Backend.Fx.RandomData { public class LoremIpsumGenerator : Generator { - private static string[] _words = new[] + private static readonly string[] _words = { - "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "proin", "eget", "iaculis", "quam", "pellentesque", "elementum", "gravida", "nulla", - "at", "tincidunt", "donec", "vulputate", "velit", "sapien", "a", "auctor", "justo", "id", "nunc", "et", "consequat", "magna", "in", "blandit", "ut", "eros", - "tempus", "condimentum", "sem", "ac", "feugiat", "tellus", "curabitur", "aliquet", "ultrices", "arcu", "eu", "lacinia", "aliquam", "integer", "non", "venenatis", - "sed", "accumsan", "massa", "nibh", "vestibulum", "nec", "porta", "libero", "vel", "ex", "molestie", "pretium", "dignissim", "ligula", "maximus", "placerat", - "nisl", "felis", "fringilla", "efficitur", "mi", "nam", "vitae", "orci", "suscipit", "porttitor", "leo", "posuere", "sollicitudin", "dictum", "tristique", "dui", - "urna", "quis", "quisque", "semper", "diam", "pulvinar", "erat", "ornare", "maecenas", "euismod", "odio", "tortor", "cursus", "convallis", "enim", "sodales", - "facilisis", "faucibus", "fusce", "scelerisque", "purus", "praesent", "interdum", "turpis", "mauris", "duis", "finibus", "augue", "nullam", "mollis", "lacus", - "egestas", "metus", "mattis", "morbi", "laoreet", "bibendum", "phasellus", "risus", "neque", "volutpat", "lobortis", "malesuada", "sagittis", "rhoncus", "est", - "imperdiet", "aenean", "fermentum", "varius", "vivamus", "suspendisse", "commodo", "luctus", "dapibus", "ullamcorper", "viverra", "congue", "hendrerit", "pharetra", + "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "proin", "eget", "iaculis", + "quam", "pellentesque", "elementum", "gravida", "nulla", + "at", "tincidunt", "donec", "vulputate", "velit", "sapien", "a", "auctor", "justo", "id", "nunc", "et", + "consequat", "magna", "in", "blandit", "ut", "eros", + "tempus", "condimentum", "sem", "ac", "feugiat", "tellus", "curabitur", "aliquet", "ultrices", "arcu", "eu", + "lacinia", "aliquam", "integer", "non", "venenatis", + "sed", "accumsan", "massa", "nibh", "vestibulum", "nec", "porta", "libero", "vel", "ex", "molestie", + "pretium", "dignissim", "ligula", "maximus", "placerat", + "nisl", "felis", "fringilla", "efficitur", "mi", "nam", "vitae", "orci", "suscipit", "porttitor", "leo", + "posuere", "sollicitudin", "dictum", "tristique", "dui", + "urna", "quis", "quisque", "semper", "diam", "pulvinar", "erat", "ornare", "maecenas", "euismod", "odio", + "tortor", "cursus", "convallis", "enim", "sodales", + "facilisis", "faucibus", "fusce", "scelerisque", "purus", "praesent", "interdum", "turpis", "mauris", + "duis", "finibus", "augue", "nullam", "mollis", "lacus", + "egestas", "metus", "mattis", "morbi", "laoreet", "bibendum", "phasellus", "risus", "neque", "volutpat", + "lobortis", "malesuada", "sagittis", "rhoncus", "est", + "imperdiet", "aenean", "fermentum", "varius", "vivamus", "suspendisse", "commodo", "luctus", "dapibus", + "ullamcorper", "viverra", "congue", "hendrerit", "pharetra", "tempor", "eleifend", "lectus", "te" }; @@ -33,6 +42,6 @@ public static string Generate(int minWords, int maxWords, bool asSentence) } return loremIpsumText; - } + } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs b/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs index 094e7a25..f02ddef7 100644 --- a/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs @@ -9,13 +9,15 @@ public static string Generate() return new MobileLineGenerator().First(); } - protected override string Next() { - var generated = Numbers.MobileNetworks.Random(); - while (generated.Length < TestRandom.Instance.Next(11)) generated += Numbers.Ciphers.Random(); + string generated = Numbers.MobileNetworks.Random(); + while (generated.Length < TestRandom.Instance.Next(11)) + { + generated += Numbers.Ciphers.Random(); + } return generated; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/Names.cs b/src/abstractions/Backend.Fx/RandomData/Names.cs index 31e5bdd4..53b2991b 100644 --- a/src/abstractions/Backend.Fx/RandomData/Names.cs +++ b/src/abstractions/Backend.Fx/RandomData/Names.cs @@ -1,4 +1,6 @@ -namespace Backend.Fx.RandomData +// ReSharper disable StringLiteralTypo + +namespace Backend.Fx.RandomData { public static class Names { @@ -5755,4 +5757,4 @@ public static class Names "Switzerland" }; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/Numbers.cs b/src/abstractions/Backend.Fx/RandomData/Numbers.cs index 182cd7c8..c8a7be07 100644 --- a/src/abstractions/Backend.Fx/RandomData/Numbers.cs +++ b/src/abstractions/Backend.Fx/RandomData/Numbers.cs @@ -6,7 +6,9 @@ namespace Backend.Fx.RandomData public static class Numbers { public static readonly string[] Ciphers = Enumerable.Range(0, 10).Select(i => i.ToString()).ToArray(); - public static readonly string[] PostalCodes = Enumerable.Range(9000, 90999).Select(i => i.ToString("00000")).ToArray(); + + public static readonly string[] PostalCodes + = Enumerable.Range(9000, 90999).Select(i => i.ToString("00000")).ToArray(); public static readonly string[] LandLineNetworks = { @@ -5286,13 +5288,19 @@ public static string RandomPostalCode() public static string RandomHouseNumber() { - var next = TestRandom.Next(100); - var nr = TestRandom.Next(100); - if (next < 10) return $"{nr} - {nr + TestRandom.Next(1, 5)}"; + int next = TestRandom.Next(100); + int nr = TestRandom.Next(100); + if (next < 10) + { + return $"{nr} - {nr + TestRandom.Next(1, 5)}"; + } - if (next < 30) return nr.ToString(CultureInfo.InvariantCulture) + "abcd".Random(); + if (next < 30) + { + return nr.ToString(CultureInfo.InvariantCulture) + "abcd".Random(); + } return nr.ToString(CultureInfo.InvariantCulture); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/TestAddress.cs b/src/abstractions/Backend.Fx/RandomData/TestAddress.cs index 735e3f9e..347fff28 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestAddress.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestAddress.cs @@ -33,4 +33,4 @@ protected override IEnumerable GetEqualityComponents() yield return Country; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs index 588d99f2..c3f49822 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs @@ -9,7 +9,6 @@ public static TestAddress Generate() return new TestAddressGenerator().First(); } - protected override TestAddress Next() { return new TestAddress( @@ -20,4 +19,4 @@ protected override TestAddress Next() Names.Countries.Random()); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/TestChemical.cs b/src/abstractions/Backend.Fx/RandomData/TestChemical.cs index af09d77e..f2b4edb0 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestChemical.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestChemical.cs @@ -5,7 +5,14 @@ namespace Backend.Fx.RandomData { public class TestChemical { - public TestChemical(string name, string description, string alternativeNames, string formula, decimal molecularWeight, string casRegistryNumber, string molFile) + public TestChemical( + string name, + string description, + string alternativeNames, + string formula, + decimal molecularWeight, + string casRegistryNumber, + string molFile) { Name = name; Description = description; @@ -17,184 +24,297 @@ public TestChemical(string name, string description, string alternativeNames, st } public string Name { get; } + public string Description { get; } + public string AlternativeNames { get; } + public string Formula { get; } + public decimal MolecularWeight { get; } + public string CasRegistryNumber { get; } + public string MolFile { get; } public string[] AlternativeNamesArray { - get { return AlternativeNames?.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); } + get + { + return AlternativeNames?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()) + .ToArray(); + } } // data taken from http://webbook.nist.gov/chemistry/name-ser.html public static TestChemical[] All { get; } = { //name, description, altName, formula, mol weight, casNo, mol file - new TestChemical("1,2-Dichloroethan", - "1,2-Dichlorethan (Ethylendichlorid, EDC) ist eine farblose, brennbare und giftige Flüssigkeit mit chloroformartigem Geruch. Diese chemische Verbindung gehört zu den Chlorkohlenwasserstoffen.", - "α,β-Dichloroethane; s-Dichloroethane; Brocide; Dutch liquid; Ethylene chloride; Ethylene dichloride; Freon 150; Glycol dichloride; 1,2-Bichloroethane; 1,2-Dichlorethane; 1,2-Dichloroethane; CH2ClCH2Cl; sym-Dichloroethane; Aethylenchlorid; Bichlorure D'ethylene; Borer sol; Chlorure D'ethylene; Cloruro di ethene; 1,2-DCE; Destruxol borer-sol; 1,2-Dichloorethaan; 1,2-Dichlor-aethan; Dichloremulsion; Di-chlor-mulsion; Dichloro-1,2-ethane; 1,2-Dicloroetano; Dutch oil; EDC; ENT 1,656; Ethane dichloride; Ethyleendichloride; 1,2-Ethylene dichloride; NCI-C00511; Rcra waste number U077; UN 1184; DCE; EDC (halocarbon); HCC 150; 1,2-dichloroethane (ethylene dichloride)", - "C2H4Cl2", 98.959m, "107-06-2", - "\n\n\n 8 7 0 0 0 1 V2000\n 2.3785 0.7551 0.8326 C 0 0 0 0 0 0 0 0 0\n 1.5134 1.8542 1.3880 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.1383 1.9817 Cl 0 0 0 0 0 0 0 0 0\n 3.8896 1.4714 0.2337 Cl 0 0 0 0 0 0 0 0 0\n 1.8822 0.2254 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.6233 0.0000 1.6007 H 0 0 0 0 0 0 0 0 0\n 1.2715 2.6111 0.6209 H 0 0 0 0 0 0 0 0 0\n 2.0083 2.3814 2.2230 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\nM END\n"), - new TestChemical("1-Buten", - "Butene (auch Butylene) sind eine Gruppe von vier isomeren Kohlenwasserstoffen mit der allgemeinen Summenformel C4H8, die über eine C–C-Doppelbindung verfügen. Sie zählen damit zu den Alkenen. Zwei der Isomere unterscheiden sich durch cis-trans-Isomerie. Butene sind unter Standardbedingungen farblose, brennbare Gase mit einer größeren Dichte als Luft. Unter Druck lassen sich die Isomere verflüssigen. Sie wirken in höheren Konzentrationen narkotisierend und erstickend. Mit Luft bilden sie explosive Gemische.", - "α-Butene; α-Butylene; But-1-ene; Butene-1; Ethylethylene; 1-Butylene; 1-C4H8", "C4H8", 56.1063m, "106-98-9", - "\n\n\n 12 11 0 0 0 1 V2000\n 1.3702 2.1763 1.6455 C 0 0 0 0 0 0 0 0 0\n 0.4451 2.8431 0.9657 C 0 0 0 0 0 0 0 0 0\n 1.9783 0.9053 1.1655 C 0 0 0 0 0 0 0 0 0\n 3.4886 0.9497 1.2530 C 0 0 0 0 0 0 0 0 0\n 1.7309 2.5402 2.6153 H 0 0 0 0 0 0 0 0 0\n 0.0000 3.7633 1.3313 H 0 0 0 0 0 0 0 0 0\n 0.0658 2.5199 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.5835 0.0745 1.7845 H 0 0 0 0 0 0 0 0 0\n 1.6643 0.6803 0.1265 H 0 0 0 0 0 0 0 0 0\n 3.9327 0.0000 0.9290 H 0 0 0 0 0 0 0 0 0\n 3.9065 1.7417 0.6183 H 0 0 0 0 0 0 0 0 0\n 3.8311 1.1398 2.2789 H 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0\n 1 3 1 0 0 0\n 1 5 1 0 0 0\n 2 6 1 0 0 0\n 2 7 1 0 0 0\n 3 4 1 0 0 0\n 3 8 1 0 0 0\n 3 9 1 0 0 0\n 4 10 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\nM END\n"), - new TestChemical("1-Hexen", - "Hexene (Betonung auf der zweiten Silbe) sind chemische Verbindungen aus der Gruppe der Alkene mit der Summenformel C6H12. Der Wortstamm Hex weist auf die sechs Kohlenstoffatome, die Endung en auf die Doppelbindung zwischen zwei der Kohlenstoffatome hin. Es existieren verschiedene Isomere, die sich in der Position der Doppelbindung und dem Vorhandensein bzw. der Lage einer Verzweigung der Kohlenstoffkette unterscheiden. Die in der Industrie am häufigsten eingesetzte Verbindung ist 1-Hexen, welche z. B. als Comonomer bei der Produktion von Polyethen eingesetzt wird.", - "Hexene-1; 1-n-Hexene; 1-C6H12; Butylethylene; Hexene; Hex-1-ene; UN 2370; Hexylene; Neodene 6 XHP; NSC 74121; Dialene 6", "C6H12", 84.1595m, - "592-41-6", - "\n\n\n 18 17 0 0 0 0 0 0 0 0 V2000\n -2.3439 -0.9761 0.0000 H 0000000000000000000\n -2.5829 -2.8063 0.0000 H 0000000000000000000\n -1.8943 -1.9654 0.0000 C 0000000000000000000\n -0.1940 -3.1757 0.0000 H 0000000000000000000\n -0.5736 -2.1523 0.0000 C 0000000000000000000\n 1.1457 -1.2481 0.8727 H 0000000000000000000\n 1.1457 -1.2481 -0.8727 H 0000000000000000000\n 0.4932 -1.0856 0.0000 C 0000000000000000000\n -0.6369 0.5346 -0.8794 H 0000000000000000000\n -0.6369 0.5346 0.8794 H 0000000000000000000\n 0.0000 0.3648 0.0000 C 0000000000000000000\n 1.7839 1.2154 0.8785 H 0000000000000000000\n 1.7839 1.2154 -0.8785 H 0000000000000000000\n 1.1455 1.3862 0.0000 C 0000000000000000000\n 0.0421 3.0486 -0.8848 H 0000000000000000000\n 0.0421 3.0486 0.8848 H 0000000000000000000\n 1.4941 3.5435 0.0000 H 0000000000000000000\n 0.6554 2.8379 0.0000 C 0000000000000000000\n 1 3 1 0 0 0\n 2 3 1 0 0 0\n 3 5 2 0 0 0\n 4 5 1 0 0 0\n 5 8 1 0 0 0\n 6 8 1 0 0 0\n 7 8 1 0 0 0\n 8 11 1 0 0 0\n 9 11 1 0 0 0\n 10 11 1 0 0 0\n 11 14 1 0 0 0\n 12 14 1 0 0 0\n 13 14 1 0 0 0\n 14 18 1 0 0 0\n 15 18 1 0 0 0\n 16 18 1 0 0 0\n 17 18 1 0 0 0\nM END\n"), - new TestChemical("2,3-Dimethylpentan", - "3-Methylhexan und 2,3-Dimethylpentan sind chirale Verbindungen, von denen zwei Enantiomere existieren; Chiralitätszentrum ist das C3, das als Substituenten Wasserstoff und je einen Methyl-, Ethyl- und Propyl- bzw. Isopropylrest trägt. Sie sind die einfachsten chiralen Alkane.", - "2,3-Dimethylpentane; 3,4-Dimethylpentane", "C7H16", 100.2019m, "565-59-3", - "\n\n\n 23 22 0 0 0 1 V2000\n 4.3751 2.6128 2.7529 C 0 0 0 0 0 0 0 0 0\n 3.2736 1.9976 1.8651 C 0 0 0 0 0 0 0 0 0\n 1.9284 2.6959 2.0896 C 0 0 0 0 0 0 0 0 0\n 3.1364 0.5066 2.1417 C 0 0 0 0 0 0 0 0 0\n 4.5445 4.1009 2.4849 C 0 0 0 0 0 0 0 0 0\n 5.7094 1.9144 2.5314 C 0 0 0 0 0 0 0 0 0\n 0.9524 2.4445 0.9624 C 0 0 0 0 0 0 0 0 0\n 4.0701 2.4735 3.8192 H 0 0 0 0 0 0 0 0 0\n 3.5772 2.1391 0.7982 H 0 0 0 0 0 0 0 0 0\n 1.4902 2.3691 3.0539 H 0 0 0 0 0 0 0 0 0\n 2.0935 3.7916 2.1977 H 0 0 0 0 0 0 0 0 0\n 2.7404 0.3139 3.1481 H 0 0 0 0 0 0 0 0 0\n 2.4601 0.0256 1.4233 H 0 0 0 0 0 0 0 0 0\n 4.1132 0.0000 2.0752 H 0 0 0 0 0 0 0 0 0\n 5.2661 4.5496 3.1789 H 0 0 0 0 0 0 0 0 0\n 4.9040 4.2910 1.4645 H 0 0 0 0 0 0 0 0 0\n 3.5891 4.6380 2.5995 H 0 0 0 0 0 0 0 0 0\n 6.1048 2.1059 1.5247 H 0 0 0 0 0 0 0 0 0\n 6.4620 2.2562 3.2527 H 0 0 0 0 0 0 0 0 0\n 5.6092 0.8222 2.6385 H 0 0 0 0 0 0 0 0 0\n 0.7285 1.3752 0.8458 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.9597 1.1415 H 0 0 0 0 0 0 0 0 0\n 1.3427 2.8017 0.0000 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 4 1 0 0 0\n 2 9 1 0 0 0\n 3 7 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\nM END\n"), - new TestChemical("Essigsäure", - "Essigsäure (systematisch Ethansäure, lateinisch acidum aceticum) ist eine farblose, flüssige, ätzende und typisch riechende Carbonsäure der Zusammensetzung C2H4O2 (Halbstrukturformel CH3COOH). Als Lebensmittelzusatzstoff trägt sie die E-Nummer E 260. Wässrige Lösungen der Essigsäure werden trivial nur Essig und reine Essigsäure Eisessig genannt. Die Salze und Ester der Essigsäure heißen Acetate oder (systematisch) Ethanoate", - "Ethanoic acid; Ethylic acid; Glacial acetic acid; Methanecarboxylic acid; Vinegar acid; CH3COOH; Acetasol; Acide acetique; Acido acetico; Azijnzuur; Essigsaeure; Octowy kwas; Acetic acid, glacial; Kyselina octova; UN 2789; Aci-jel; Shotgun; Ethanoic acid monomer; NSC 132953", - "C2H4O2", 60.052m, "64-19-7", - "\n\n\n 8 7 0 0 0 0 0 0 0 0999 V2000\n 0.7724 0.9670 1.0069 C 0 0 0 0 0\n 2.0576 1.7541 0.9321 C 0 0 0 0 0\n 3.0833 0.9883 0.4751 O 0 0 0 0 0\n 2.1974 2.9214 1.2200 O 0 0 0 0 0\n 0.0114 1.5612 1.5141 H 0 0 0 0 0\n 0.4326 0.7231 -0.0064 H 0 0 0 0 0\n 0.9331 0.0221 1.5356 H 0 0 0 0 0\n 3.8606 1.5761 0.4447 H 0 0 0 0 0\n 6 1 1 0 0 0\n 2 1 1 0 0 0\n 1 5 1 0 0 0\n 1 7 1 0 0 0\n 3 2 1 0 0 0\n 2 4 2 0 0 0\n 8 3 1 0 0 0\nM END\n"), + new TestChemical( + "1,2-Dichloroethan", + "1,2-Dichlorethan (Ethylendichlorid, EDC) ist eine farblose, brennbare und giftige Flüssigkeit mit chloroformartigem Geruch. Diese chemische Verbindung gehört zu den Chlorkohlenwasserstoffen.", + "α,β-Dichloroethane; s-Dichloroethane; Brocide; Dutch liquid; Ethylene chloride; Ethylene dichloride; Freon 150; Glycol dichloride; 1,2-Bichloroethane; 1,2-Dichlorethane; 1,2-Dichloroethane; CH2ClCH2Cl; sym-Dichloroethane; Aethylenchlorid; Bichlorure D'ethylene; Borer sol; Chlorure D'ethylene; Cloruro di ethene; 1,2-DCE; Destruxol borer-sol; 1,2-Dichloorethaan; 1,2-Dichlor-aethan; Dichloremulsion; Di-chlor-mulsion; Dichloro-1,2-ethane; 1,2-Dicloroetano; Dutch oil; EDC; ENT 1,656; Ethane dichloride; Ethyleendichloride; 1,2-Ethylene dichloride; NCI-C00511; Rcra waste number U077; UN 1184; DCE; EDC (halocarbon); HCC 150; 1,2-dichloroethane (ethylene dichloride)", + "C2H4Cl2", + 98.959m, + "107-06-2", + "\n\n\n 8 7 0 0 0 1 V2000\n 2.3785 0.7551 0.8326 C 0 0 0 0 0 0 0 0 0\n 1.5134 1.8542 1.3880 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.1383 1.9817 Cl 0 0 0 0 0 0 0 0 0\n 3.8896 1.4714 0.2337 Cl 0 0 0 0 0 0 0 0 0\n 1.8822 0.2254 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.6233 0.0000 1.6007 H 0 0 0 0 0 0 0 0 0\n 1.2715 2.6111 0.6209 H 0 0 0 0 0 0 0 0 0\n 2.0083 2.3814 2.2230 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\nM END\n"), + new TestChemical( + "1-Buten", + "Butene (auch Butylene) sind eine Gruppe von vier isomeren Kohlenwasserstoffen mit der allgemeinen Summenformel C4H8, die über eine C–C-Doppelbindung verfügen. Sie zählen damit zu den Alkenen. Zwei der Isomere unterscheiden sich durch cis-trans-Isomerie. Butene sind unter Standardbedingungen farblose, brennbare Gase mit einer größeren Dichte als Luft. Unter Druck lassen sich die Isomere verflüssigen. Sie wirken in höheren Konzentrationen narkotisierend und erstickend. Mit Luft bilden sie explosive Gemische.", + "α-Butene; α-Butylene; But-1-ene; Butene-1; Ethylethylene; 1-Butylene; 1-C4H8", + "C4H8", + 56.1063m, + "106-98-9", + "\n\n\n 12 11 0 0 0 1 V2000\n 1.3702 2.1763 1.6455 C 0 0 0 0 0 0 0 0 0\n 0.4451 2.8431 0.9657 C 0 0 0 0 0 0 0 0 0\n 1.9783 0.9053 1.1655 C 0 0 0 0 0 0 0 0 0\n 3.4886 0.9497 1.2530 C 0 0 0 0 0 0 0 0 0\n 1.7309 2.5402 2.6153 H 0 0 0 0 0 0 0 0 0\n 0.0000 3.7633 1.3313 H 0 0 0 0 0 0 0 0 0\n 0.0658 2.5199 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.5835 0.0745 1.7845 H 0 0 0 0 0 0 0 0 0\n 1.6643 0.6803 0.1265 H 0 0 0 0 0 0 0 0 0\n 3.9327 0.0000 0.9290 H 0 0 0 0 0 0 0 0 0\n 3.9065 1.7417 0.6183 H 0 0 0 0 0 0 0 0 0\n 3.8311 1.1398 2.2789 H 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0\n 1 3 1 0 0 0\n 1 5 1 0 0 0\n 2 6 1 0 0 0\n 2 7 1 0 0 0\n 3 4 1 0 0 0\n 3 8 1 0 0 0\n 3 9 1 0 0 0\n 4 10 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\nM END\n"), + new TestChemical( + "1-Hexen", + "Hexene (Betonung auf der zweiten Silbe) sind chemische Verbindungen aus der Gruppe der Alkene mit der Summenformel C6H12. Der Wortstamm Hex weist auf die sechs Kohlenstoffatome, die Endung en auf die Doppelbindung zwischen zwei der Kohlenstoffatome hin. Es existieren verschiedene Isomere, die sich in der Position der Doppelbindung und dem Vorhandensein bzw. der Lage einer Verzweigung der Kohlenstoffkette unterscheiden. Die in der Industrie am häufigsten eingesetzte Verbindung ist 1-Hexen, welche z. B. als Comonomer bei der Produktion von Polyethen eingesetzt wird.", + "Hexene-1; 1-n-Hexene; 1-C6H12; Butylethylene; Hexene; Hex-1-ene; UN 2370; Hexylene; Neodene 6 XHP; NSC 74121; Dialene 6", + "C6H12", + 84.1595m, + "592-41-6", + "\n\n\n 18 17 0 0 0 0 0 0 0 0 V2000\n -2.3439 -0.9761 0.0000 H 0000000000000000000\n -2.5829 -2.8063 0.0000 H 0000000000000000000\n -1.8943 -1.9654 0.0000 C 0000000000000000000\n -0.1940 -3.1757 0.0000 H 0000000000000000000\n -0.5736 -2.1523 0.0000 C 0000000000000000000\n 1.1457 -1.2481 0.8727 H 0000000000000000000\n 1.1457 -1.2481 -0.8727 H 0000000000000000000\n 0.4932 -1.0856 0.0000 C 0000000000000000000\n -0.6369 0.5346 -0.8794 H 0000000000000000000\n -0.6369 0.5346 0.8794 H 0000000000000000000\n 0.0000 0.3648 0.0000 C 0000000000000000000\n 1.7839 1.2154 0.8785 H 0000000000000000000\n 1.7839 1.2154 -0.8785 H 0000000000000000000\n 1.1455 1.3862 0.0000 C 0000000000000000000\n 0.0421 3.0486 -0.8848 H 0000000000000000000\n 0.0421 3.0486 0.8848 H 0000000000000000000\n 1.4941 3.5435 0.0000 H 0000000000000000000\n 0.6554 2.8379 0.0000 C 0000000000000000000\n 1 3 1 0 0 0\n 2 3 1 0 0 0\n 3 5 2 0 0 0\n 4 5 1 0 0 0\n 5 8 1 0 0 0\n 6 8 1 0 0 0\n 7 8 1 0 0 0\n 8 11 1 0 0 0\n 9 11 1 0 0 0\n 10 11 1 0 0 0\n 11 14 1 0 0 0\n 12 14 1 0 0 0\n 13 14 1 0 0 0\n 14 18 1 0 0 0\n 15 18 1 0 0 0\n 16 18 1 0 0 0\n 17 18 1 0 0 0\nM END\n"), + new TestChemical( + "2,3-Dimethylpentan", + "3-Methylhexan und 2,3-Dimethylpentan sind chirale Verbindungen, von denen zwei Enantiomere existieren; Chiralitätszentrum ist das C3, das als Substituenten Wasserstoff und je einen Methyl-, Ethyl- und Propyl- bzw. Isopropylrest trägt. Sie sind die einfachsten chiralen Alkane.", + "2,3-Dimethylpentane; 3,4-Dimethylpentane", + "C7H16", + 100.2019m, + "565-59-3", + "\n\n\n 23 22 0 0 0 1 V2000\n 4.3751 2.6128 2.7529 C 0 0 0 0 0 0 0 0 0\n 3.2736 1.9976 1.8651 C 0 0 0 0 0 0 0 0 0\n 1.9284 2.6959 2.0896 C 0 0 0 0 0 0 0 0 0\n 3.1364 0.5066 2.1417 C 0 0 0 0 0 0 0 0 0\n 4.5445 4.1009 2.4849 C 0 0 0 0 0 0 0 0 0\n 5.7094 1.9144 2.5314 C 0 0 0 0 0 0 0 0 0\n 0.9524 2.4445 0.9624 C 0 0 0 0 0 0 0 0 0\n 4.0701 2.4735 3.8192 H 0 0 0 0 0 0 0 0 0\n 3.5772 2.1391 0.7982 H 0 0 0 0 0 0 0 0 0\n 1.4902 2.3691 3.0539 H 0 0 0 0 0 0 0 0 0\n 2.0935 3.7916 2.1977 H 0 0 0 0 0 0 0 0 0\n 2.7404 0.3139 3.1481 H 0 0 0 0 0 0 0 0 0\n 2.4601 0.0256 1.4233 H 0 0 0 0 0 0 0 0 0\n 4.1132 0.0000 2.0752 H 0 0 0 0 0 0 0 0 0\n 5.2661 4.5496 3.1789 H 0 0 0 0 0 0 0 0 0\n 4.9040 4.2910 1.4645 H 0 0 0 0 0 0 0 0 0\n 3.5891 4.6380 2.5995 H 0 0 0 0 0 0 0 0 0\n 6.1048 2.1059 1.5247 H 0 0 0 0 0 0 0 0 0\n 6.4620 2.2562 3.2527 H 0 0 0 0 0 0 0 0 0\n 5.6092 0.8222 2.6385 H 0 0 0 0 0 0 0 0 0\n 0.7285 1.3752 0.8458 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.9597 1.1415 H 0 0 0 0 0 0 0 0 0\n 1.3427 2.8017 0.0000 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 4 1 0 0 0\n 2 9 1 0 0 0\n 3 7 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\nM END\n"), + new TestChemical( + "Essigsäure", + "Essigsäure (systematisch Ethansäure, lateinisch acidum aceticum) ist eine farblose, flüssige, ätzende und typisch riechende Carbonsäure der Zusammensetzung C2H4O2 (Halbstrukturformel CH3COOH). Als Lebensmittelzusatzstoff trägt sie die E-Nummer E 260. Wässrige Lösungen der Essigsäure werden trivial nur Essig und reine Essigsäure Eisessig genannt. Die Salze und Ester der Essigsäure heißen Acetate oder (systematisch) Ethanoate", + "Ethanoic acid; Ethylic acid; Glacial acetic acid; Methanecarboxylic acid; Vinegar acid; CH3COOH; Acetasol; Acide acetique; Acido acetico; Azijnzuur; Essigsaeure; Octowy kwas; Acetic acid, glacial; Kyselina octova; UN 2789; Aci-jel; Shotgun; Ethanoic acid monomer; NSC 132953", + "C2H4O2", + 60.052m, + "64-19-7", + "\n\n\n 8 7 0 0 0 0 0 0 0 0999 V2000\n 0.7724 0.9670 1.0069 C 0 0 0 0 0\n 2.0576 1.7541 0.9321 C 0 0 0 0 0\n 3.0833 0.9883 0.4751 O 0 0 0 0 0\n 2.1974 2.9214 1.2200 O 0 0 0 0 0\n 0.0114 1.5612 1.5141 H 0 0 0 0 0\n 0.4326 0.7231 -0.0064 H 0 0 0 0 0\n 0.9331 0.0221 1.5356 H 0 0 0 0 0\n 3.8606 1.5761 0.4447 H 0 0 0 0 0\n 6 1 1 0 0 0\n 2 1 1 0 0 0\n 1 5 1 0 0 0\n 1 7 1 0 0 0\n 3 2 1 0 0 0\n 2 4 2 0 0 0\n 8 3 1 0 0 0\nM END\n"), new TestChemical( "Aceton", "Aceton [at͡səˈtoːn] (auch: Azeton) ist der Trivialname für die organisch-chemische Verbindung Propanon bzw. Dimethylketon. Aceton ist eine farblose Flüssigkeit und findet Verwendung als polares, aprotisches Lösungsmittel und als Ausgangsstoff für viele Synthesen der organischen Chemie. Es ist mit seinem Strukturmerkmal der Carbonylgruppe (>C=O), die zwei Methylgruppen trägt, das einfachste Keton.", "2-Propanone; β-Ketopropane; Dimethyl ketone; Dimethylformaldehyde; Methyl ketone; Propanone; Pyroacetic ether; (CH3)2CO; Dimethylketal; Ketone propane; Ketone, dimethyl-; Chevron acetone; Rcra waste number U002; UN 1090; Sasetone; Propan-2-one; NSC 135802", - "C3H6O", 58.0791m, "67-64-1", + "C3H6O", + 58.0791m, + "67-64-1", "\n\n\n 10 9 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.1857 C 0000000000000000000\n 0.0000 0.0000 1.4013 O 0000000000000000000\n 0.0000 1.2929 -0.6150 C 0000000000000000000\n 0.0000 -1.2929 -0.6150 C 0000000000000000000\n 0.0000 2.1487 0.0628 H 0000000000000000000\n 0.0000 -2.1487 0.0628 H 0000000000000000000\n 0.8810 1.3407 -1.2674 H 0000000000000000000\n -0.8810 1.3407 -1.2674 H 0000000000000000000\n -0.8810 -1.3407 -1.2674 H 0000000000000000000\n 0.8810 -1.3407 -1.2674 H 0000000000000000000\n 1 2 2 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 3 5 1 0 0 0\n 3 7 1 0 0 0\n 3 8 1 0 0 0\n 4 6 1 0 0 0\n 4 9 1 0 0 0\n 4 10 1 0 0 0\nM END\n"), - new TestChemical("Acetonitril", "Acetonitril ist ein organisches Lösungsmittel und gehört zur Stoffgruppe der Nitrile.", - "Cyanomethane; Ethanenitrile; Ethyl nitrile; Methane, cyano-; Methanecarbonitrile; Methyl cyanide; CH3CN; Acetonitril; Cyanure de methyl; USAF EK-488; Methylkyanid; NA 1648; NCI-C60822; Rcra waste number U003; UN 1648; Ethanonitrile", - "C2H3N", 41.0519m, "75-05-8", - "\n\n\n 6 5 0 0 0 1 V2000\n 0.4219 0.8958 0.5193 C 0 0 0 0 0 0 0 0 0\n 1.8612 0.9422 0.4897 C 0 0 0 0 0 0 0 0 0\n 3.0198 0.9796 0.4659 N 0 0 0 0 0 0 0 0 0\n 0.0000 1.7811 0.0257 H 0 0 0 0 0 0 0 0 0\n 0.0568 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0607 0.8694 1.5558 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 3 0 0 0\nM END\n"), + new TestChemical( + "Acetonitril", + "Acetonitril ist ein organisches Lösungsmittel und gehört zur Stoffgruppe der Nitrile.", + "Cyanomethane; Ethanenitrile; Ethyl nitrile; Methane, cyano-; Methanecarbonitrile; Methyl cyanide; CH3CN; Acetonitril; Cyanure de methyl; USAF EK-488; Methylkyanid; NA 1648; NCI-C60822; Rcra waste number U003; UN 1648; Ethanonitrile", + "C2H3N", + 41.0519m, + "75-05-8", + "\n\n\n 6 5 0 0 0 1 V2000\n 0.4219 0.8958 0.5193 C 0 0 0 0 0 0 0 0 0\n 1.8612 0.9422 0.4897 C 0 0 0 0 0 0 0 0 0\n 3.0198 0.9796 0.4659 N 0 0 0 0 0 0 0 0 0\n 0.0000 1.7811 0.0257 H 0 0 0 0 0 0 0 0 0\n 0.0568 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0607 0.8694 1.5558 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 3 0 0 0\nM END\n"), new TestChemical( "Benzen", "Benzol (nach der IUPAC Benzen) ist eine flüssige organische Verbindung mit einem charakteristischen aromatischen Geruch. Die Verbindung mit der Summenformel C6H6 ist ein aromatischer Kohlenwasserstoff und das einfachste und zugleich klassische Beispiel für die Aromatizität bestimmter Verbindungen. Benzol ist mischbar mit fast allen organischen Solventien, jedoch kaum mit Wasser. Als Lösungsmittel hat Benzol seine Bedeutung verloren, da es krebserregend ist. Als mutagenes Klastogen wirken Benzol bzw. dessen Metabolite als Gift, welches Chromosomenaberrationen hervorrufen kann.", "[6]Annulene; Benzol; Benzole; Coal naphtha; Cyclohexatriene; Phenyl hydride; Pyrobenzol; Pyrobenzole; Benzolene; Bicarburet of hydrogen; Carbon oil; Mineral naphtha; Motor benzol; Benzeen; Benzen; Benzin; Benzine; Benzolo; Fenzen; NCI-C55276; Phene; Rcra waste number U019; UN 1114; NSC 67315; 1,3,5-Cyclohexatriene", - "C6H6", 78.1118m, "71-43-2", + "C6H6", + 78.1118m, + "71-43-2", "\n\n\n 12 12 0 0 0 1 V2000\n 3.2883 3.3891 0.2345 C 0 0 0 0 0 0 0 0 0\n 1.9047 3.5333 0.2237 C 0 0 0 0 0 0 0 0 0\n 3.8560 2.1213 0.1612 C 0 0 0 0 0 0 0 0 0\n 1.0888 2.4099 0.1396 C 0 0 0 0 0 0 0 0 0\n 3.0401 0.9977 0.0771 C 0 0 0 0 0 0 0 0 0\n 1.6565 1.1421 0.0663 C 0 0 0 0 0 0 0 0 0\n 3.9303 4.2734 0.3007 H 0 0 0 0 0 0 0 0 0\n 1.4582 4.5312 0.2815 H 0 0 0 0 0 0 0 0 0\n 4.9448 2.0077 0.1699 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.5234 0.1311 H 0 0 0 0 0 0 0 0 0\n 3.4870 0.0000 0.0197 H 0 0 0 0 0 0 0 0 0\n 1.0145 0.2578 0.0000 H 0 0 0 0 0 0 0 0 0\n 2 1 2 0 0 0\n 1 3 1 0 0 0\n 1 7 1 0 0 0\n 4 2 1 0 0 0\n 2 8 1 0 0 0\n 3 5 2 0 0 0\n 3 9 1 0 0 0\n 6 4 2 0 0 0\n 4 10 1 0 0 0\n 5 6 1 0 0 0\n 5 11 1 0 0 0\n 6 12 1 0 0 0\nM END\n"), - new TestChemical("Kohlentetrachlorid", - "Kohlenstoffgruppe oder Kohlenstoff-Silicium-Gruppe bezeichnet die 4. Hauptgruppe („Tetrele“) (nach neuer Nummerierung der IUPAC Gruppe 14) des Periodensystems. Sie umfasst die Elemente Kohlenstoff (C), Silicium (Si), Germanium (Ge), Zinn (Sn) und Blei (Pb). Auch ein radioaktives Element, das Flerovium (Fl), ist vertreten.", - "Methane, tetrachloro-; Benzinoform; Carbon chloride (CCl4); Carbona; Fasciolin; Flukoids; Freon 10; Necatorina; Perchloromethane; Tetrachlorocarbon; Tetrachloromethane; Tetrafinol; Tetraform; Tetrasol; Univerm; Vermoestricid; CCl4; Benzenoform; Carbon tet; Methane tetrachloride; Czterochlorek wegla; ENT 4,705; Halon 1040; Necatorine; R 10; Tetrachloorkoolstof; Tetrachloormetaan; Tetrachlorkohlenstoff, tetra; Tetrachlormethan; Tetrachlorure de carbone; Tetraclorometano; Tetracloruro di carbonio; Chlorid uhlicity; ENT 27164; Rcra waste number U211; UN 1846; Katharin; Seretin; Thawpit; NSC 97063; R 10 (Refrigerant)", - "CCl4", 153.823m, "56-23-5", - "\n\n\n 5 4 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.0000 C 0000000000000000000\n 1.0350 1.0350 1.0350 Cl 0000000000000000000\n -1.0350 -1.0350 1.0350 Cl 0000000000000000000\n -1.0350 1.0350 -1.0350 Cl 0000000000000000000\n 1.0350 -1.0350 -1.0350 Cl 0000000000000000000\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), - new TestChemical("Chloroform", "Chloroform (systematische Bezeichnung Trichlormethan) ist ein chlorierter Kohlenwasserstoff mit der Summenformel CHCl3.", - "Chloroform; Freon 20; Methane, trichloro-; R 20; Trichloroform; CHCl3; Formyl trichloride; Methane trichloride; Methenyl trichloride; Methyl trichloride; Chloroforme; Cloroformio; NCI-C02686; R 20 (refrigerant); Trichloormethaan; Trichlormethan; Triclorometano; Rcra waste number U044; UN 1888; NSC 77361; F 20", - "CHCl3", 119.378m, "67-66-3", - "\n\n\n 5 4 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.4548 C 0000000000000000000\n 0.0000 0.0000 1.5402 H 0000000000000000000\n 0.0000 1.7050 -0.0837 Cl 0000000000000000000\n 1.4766 -0.8525 -0.0837 Cl 0000000000000000000\n -1.4766 -0.8525 -0.0837 Cl 0000000000000000000\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), - new TestChemical("Cyclohexan", - "Cyclohexan (auch Hexahydrobenzol, Hexamethylen, Naphthen) ist eine farblose Flüssigkeit. Es ist ein Cycloalkan mit der Summenformel C6H12, das im Erdöl vorkommt und als Lösungsmittel und Grundstoff in der Synthese genutzt wird.", - "Benzene, hexahydro-; Hexahydrobenzene; Hexamethylene; Hexanaphthene; Cicloesano; Cykloheksan; Rcra waste number U056; UN 1145; NSC 406835", "C6H12", - 84.1595m, "110-82-7", - "\n\n\n 18 18 0 0 0 0 0 0 0 0 V2000\n 0.0000 -1.4672 0.2293 C 0000000000000000000\n -1.2706 0.7336 0.2293 C 0000000000000000000\n 1.2706 0.7336 0.2293 C 0000000000000000000\n 0.0000 1.4672 -0.2293 C 0000000000000000000\n -1.2706 -0.7336 -0.2293 C 0000000000000000000\n 1.2706 -0.7336 -0.2293 C 0000000000000000000\n 0.0000 -1.5355 1.3276 H 0000000000000000000\n -1.3298 0.7677 1.3276 H 0000000000000000000\n 1.3298 0.7677 1.3276 H 0000000000000000000\n 0.0000 1.5355 -1.3276 H 0000000000000000000\n -1.3298 -0.7677 -1.3276 H 0000000000000000000\n 1.3298 -0.7677 -1.3276 H 0000000000000000000\n 0.0000 -2.4985 -0.1466 H 0000000000000000000\n -2.1638 1.2493 -0.1466 H 0000000000000000000\n 2.1638 1.2493 -0.1466 H 0000000000000000000\n 0.0000 2.4985 0.1466 H 0000000000000000000\n -2.1638 -1.2493 0.1466 H 0000000000000000000\n 2.1638 -1.2493 0.1466 H 0000000000000000000\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 1 13 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 2 8 1 0 0 0\n 2 14 1 0 0 0\n 3 4 1 0 0 0\n 3 6 1 0 0 0\n 3 9 1 0 0 0\n 3 15 1 0 0 0\n 4 10 1 0 0 0\n 4 16 1 0 0 0\n 5 11 1 0 0 0\n 5 17 1 0 0 0\n 6 12 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), - new TestChemical("Cyclopenten", - "Cyclopenten ist eine organische Verbindung mit der Summenformel C5H8. Sie besteht aus einem fünfgliedrigen, ungesättigten Ring, welcher eine Doppelbindung aufweist. In der homologen Reihe der Cycloalkene steht Cyclopenten zwischen Cyclobuten und Cyclohexen. Formal handelt es sich um ein einfach hydriertes Cyclopentadien beziehungsweise ein einfach dehydriertes Cyclopentan. Cyclopenten besitzt nur wenige Anwendungen.", - "", "C5H8", 68.1170m, "142-29-0", - "\n\n\n 13 13 0 0 0 1 V2000\n 0.7026 2.1487 1.2803 C 0 0 0 0 0 0 0 0 0\n 1.9143 3.0520 1.0028 C 0 0 0 0 0 0 0 0 0\n 3.1024 2.1512 1.1173 C 0 0 0 0 0 0 0 0 0\n 2.7505 0.8940 1.4098 C 0 0 0 0 0 0 0 0 0\n 1.2685 0.7440 1.5398 C 0 0 0 0 0 0 0 0 0\n 0.1217 2.5129 2.1461 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.1470 0.4281 H 0 0 0 0 0 0 0 0 0\n 1.8684 3.5149 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.9848 3.8877 1.7230 H 0 0 0 0 0 0 0 0 0\n 4.1143 2.5269 0.9699 H 0 0 0 0 0 0 0 0 0\n 3.4198 0.0455 1.5471 H 0 0 0 0 0 0 0 0 0\n 0.8848 0.0000 0.8178 H 0 0 0 0 0 0 0 0 0\n 0.9989 0.3651 2.5427 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 5 1 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 4 2 0 0 0\n 3 10 1 0 0 0\n 4 5 1 0 0 0\n 4 11 1 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\nM END\n"), - new TestChemical("Diethylether", - "Diethylether ist der wichtigste Vertreter der organisch-chemischen Verbindungsklasse der Ether und wird deshalb häufig auch einfach als Ether (standardsprachlich und in der älteren wissenschaftlichen Literatur Äther) bezeichnet. Aufgrund der Herstellung aus Ethanol und Schwefelsäure war die historische Bezeichnung Schwefeläther, obwohl Diethylether keinen Schwefel enthält.", - "Ethane, 1,1'-oxybis-; Anaesthetic ether; Anesthesia ether; Anesthetic ether; Diethyl ether; Diethyl oxide; Ethoxyethane; Pronarcol; Solvent ether; 1,1'-Oxybisethane; (C2H5)2O; Aether; Diaethylaether; Dwuetylowy eter; Etere etilico; Ether ethylique; Ether, ethyl; Ethyl ether, tech.; Ethyl oxide; Oxyde d'ethyle; Rcra waste number U117; UN 1155; 3-Oxapentane; Ether; Ethyl ether anhydrous A.C.S.; Sulfuric ether; NSC 100036", - "C4H10O", 74.1216m, "60-29-7", - "\n\n\n 15 14 0 0 0 1 V2000\n 0.9744 2.7710 2.3574 C 0 0 0 0 0 0 0 0 0\n 0.9176 4.1589 1.7420 C 0 0 0 0 0 0 0 0 0\n 1.8106 1.8731 1.6596 O 0 0 0 0 0 0 0 0 0\n 3.2030 2.0811 1.8614 C 0 0 0 0 0 0 0 0 0\n 3.9340 1.0107 1.0734 C 0 0 0 0 0 0 0 0 0\n 0.0000 2.2572 2.2827 H 0 0 0 0 0 0 0 0 0\n 1.2519 2.8230 3.4284 H 0 0 0 0 0 0 0 0 0\n 0.1388 4.7597 2.2281 H 0 0 0 0 0 0 0 0 0\n 0.6868 4.1218 0.6694 H 0 0 0 0 0 0 0 0 0\n 1.8666 4.7007 1.8520 H 0 0 0 0 0 0 0 0 0\n 3.4420 2.0182 2.9412 H 0 0 0 0 0 0 0 0 0\n 3.4890 3.0953 1.5205 H 0 0 0 0 0 0 0 0 0\n 5.0187 1.1245 1.1931 H 0 0 0 0 0 0 0 0 0\n 3.7104 1.0671 0.0000 H 0 0 0 0 0 0 0 0 0\n 3.6667 0.0000 1.4086 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 4 1 0 0 0\n 4 5 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 5 15 1 0 0 0\nM END\n"), - new TestChemical("Ethanol", - "Ethanol (häufige Trivialnamen: Äthanol, Ethylalkohol, Alkohol) ist ein aliphatischer, einwertiger Alkohol mit der Summenformel C2H6O. Die reine Substanz ist eine bei Raumtemperatur farblose, leicht entzündliche Flüssigkeit mit einem brennenden Geschmack und einem charakteristischen, würzigen (süßlichen) Geruch. Die als Lebergift eingestufte Droge wird bei der Herstellung von Genussmitteln und alkoholischen Getränken wie Wein, Bier und Spirituosen aus kohlehydrathaltigem Material durch eine von Hefen ausgelöste Gärung in relativ großen Mengen produziert.", - "Ethyl alcohol; Alcohol; Alcohol anhydrous; Algrain; Anhydrol; Denatured ethanol; Ethyl hydrate; Ethyl hydroxide; Jaysol; Jaysol S; Methylcarbinol; SD Alchol 23-hydrogen; Tecsol; C2H5OH; Absolute ethanol; Cologne spirit; Fermentation alcohol; Grain alcohol; Molasses alcohol; Potato alcohol; Aethanol; Aethylalkohol; Alcohol, dehydrated; Alcool ethylique; Alcool etilico; Alkohol; Cologne spirits; Denatured alcohol CD-10; Denatured alcohol CD-5; Denatured alcohol CD-5a; Denatured alcohol SD-1; Denatured alcohol SD-13a; Denatured alcohol SD-17; Denatured alcohol SD-23a; Denatured alcohol SD-28; Denatured alcohol SD-3a; Denatured alcohol SD-30; Denatured alcohol SD-39b; Denatured alcohol SD-39c; Denatured alcohol SD-40m; Etanolo; Ethanol 200 proof; Ethyl alc; Etylowy alkohol; EtOH; NCI-C03134; Spirits of wine; Spirt; Alkoholu etylowego; Ethyl alcohol anhydrous; SD alcohol 23-hydrogen; UN 1170; Tecsol C; Alcare Hand Degermer; Absolute alcohol; Denatured alcohol; Ethanol, silent spirit; Ethylol; Punctilious ethyl alcohol; SD 3A", - "C2H6O", 46.0684m, "64-17-5", - "\n\n\n 9 8 0 0 0 0 0 0 0 0999 V2000\n 1.0195 0.8856 0.9752 C 0 0 0 0 0\n 1.8780 1.9882 1.5739 C 0 0 0 0 0\n 3.1989 1.4758 1.7291 O 0 0 0 0 0\n -0.0045 1.2392 0.8098 H 0 0 0 0 0\n 0.9875 0.0188 1.6438 H 0 0 0 0 0\n 1.4360 0.5612 0.0153 H 0 0 0 0 0\n 1.8717 2.8699 0.9114 H 0 0 0 0 0\n 1.4594 2.3055 2.5439 H 0 0 0 0 0\n 3.7472 2.1776 2.1115 H 0 0 0 0 0\n 6 1 1 0 0 0\n 4 1 1 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 7 2 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\nM END\n"), - new TestChemical("Essigsäureethylester", - "Essigsäureethylester, auch Ethylacetat oder Essigester, ist eine chemische Verbindung aus der Gruppe der Carbonsäureester. Es ist der Ester von Essigsäure und Ethanol. Die farblose Flüssigkeit ist ein charakteristisch nach Klebstoff riechendes Lösungsmittel, das in der chemischen Industrie und in Laboratorien oft verwendet wird.", - "Acetic acid, ethyl ester; Acetic ether; Acetidin; Acetoxyethane; Ethyl acetic ester; Ethyl ethanoate; Vinegar naphtha; CH3COOC2H5; Aethylacetat; Essigester; Ethyle (acetate d'); Etile (acetato di); Ethylacetaat; Ethylester kyseliny octove; Rcra waste number U112; UN 1173; Ethyl ester of acetic acid; 1-Acetoxyethane; NSC 70930; ac. acetic ethyl ester", - "C4H8O2", 88.1051m, "141-78-6", - "\n\n\n 14 13 0 0 0 0 0 0 0 0999 V2000\n 3.4731 2.2227 2.4832 O 0 0 0 0 0\n 2.1749 2.8339 2.4803 C 0 0 0 0 0\n 2.2477 4.1592 1.7469 C 0 0 0 0 0\n 3.7187 1.3350 1.4827 C 0 0 0 0 0\n 5.1346 0.8483 1.5545 C 0 0 0 0 0\n 2.9285 1.0057 0.6092 O 0 0 0 0 0\n 1.4145 2.1783 2.0421 H 0 0 0 0 0\n 1.8932 3.0019 3.5247 H 0 0 0 0 0\n 1.2769 4.6623 1.7528 H 0 0 0 0 0\n 2.9878 4.8162 2.2162 H 0 0 0 0 0\n 2.5643 4.0145 0.7089 H 0 0 0 0 0\n 5.1634 -0.2191 1.3199 H 0 0 0 0 0\n 5.7451 1.4089 0.8424 H 0 0 0 0 0\n 5.5400 0.9802 2.5620 H 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 5 1 0 0 0\n 4 6 2 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\nM END\n"), - new TestChemical("Ethylpropylether", "Ethylpropylether", - "Ether, ethyl propyl; Ethyl n-propyl ether; Ethyl propyl ether; Propyl ethyl ether; 1-Ethoxypropane; n-C3H7OC2H5; UN 2615", "C5H12O", 88.1482m, - "628-32-0", - "\n\n\n 18 17 0 0 0 1 V2000\n 3.1079 1.7452 1.0645 O 0 0 0 0 0 0 0 0 0\n 4.4441 1.2694 1.1114 C 0 0 0 0 0 0 0 0 0\n 5.3490 2.4893 1.2632 C 0 0 0 0 0 0 0 0 0\n 2.1504 0.7063 0.9251 C 0 0 0 0 0 0 0 0 0\n 6.8002 2.0698 1.3208 C 0 0 0 0 0 0 0 0 0\n 0.7827 1.3612 0.8832 C 0 0 0 0 0 0 0 0 0\n 4.6830 0.7077 0.1862 H 0 0 0 0 0 0 0 0 0\n 4.5718 0.5698 1.9616 H 0 0 0 0 0 0 0 0 0\n 5.0789 3.0544 2.1773 H 0 0 0 0 0 0 0 0 0\n 5.1881 3.1923 0.4219 H 0 0 0 0 0 0 0 0 0\n 2.2307 0.0000 1.7749 H 0 0 0 0 0 0 0 0 0\n 2.3473 0.1294 0.0000 H 0 0 0 0 0 0 0 0 0\n 7.4601 2.9403 1.4280 H 0 0 0 0 0 0 0 0 0\n 7.1058 1.5371 0.4105 H 0 0 0 0 0 0 0 0 0\n 6.9969 1.4029 2.1707 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.5997 0.7766 H 0 0 0 0 0 0 0 0 0\n 0.6863 2.0571 0.0395 H 0 0 0 0 0 0 0 0 0\n 0.5712 1.9289 1.7988 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 5 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 4 6 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 5 15 1 0 0 0\n 6 16 1 0 0 0\n 6 17 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), - new TestChemical("Essigsäureisopropylester", - "Essigsäureisopropylester ist eine chemische Verbindung aus der Gruppe der Carbonsäureester. Es ist eine farblose, leichtentzündliche und flüchtige Flüssigkeit.", - "Acetic acid, 1-methylethyl ester; Acetic acid, isopropyl ester; 2-Acetoxypropane; 2-Propyl acetate; CH3COOCH(CH3)2; Acetate d'isopropyle; Isopropile(acetato di); Isopropyl ethanoate; Isopropyl (acetate d'); Isopropylacetaat; Isopropylacetat; Isopropylester kyseliny octove; UN 1220; Isopropyl ester of acetic acid; sec-Propyl acetate; Acetic acid, 2-propyl ester; 1-Methylethyl acetate; NSC 9295", - "C5H10O2", 102.1317m, "108-21-4", - "\n\n\n 17 16 0 0 0 1 V2000\n 3.1938 1.0106 1.8174 O 0 0 0 0 0 0 0 0 0\n 2.0014 1.7632 2.1082 C 0 0 0 0 0 0 0 0 0\n 4.4164 1.5950 1.9641 C 0 0 0 0 0 0 0 0 0\n 0.9499 0.7230 2.4807 C 0 0 0 0 0 0 0 0 0\n 1.5706 2.5640 0.8846 C 0 0 0 0 0 0 0 0 0\n 5.5443 0.6574 1.6359 C 0 0 0 0 0 0 0 0 0\n 4.5054 2.7501 2.3330 O 0 0 0 0 0 0 0 0 0\n 2.1755 2.4482 2.9734 H 0 0 0 0 0 0 0 0 0\n 1.2515 0.1324 3.3552 H 0 0 0 0 0 0 0 0 0\n 0.7564 0.0192 1.6598 H 0 0 0 0 0 0 0 0 0\n 0.0000 1.2159 2.7240 H 0 0 0 0 0 0 0 0 0\n 1.4415 1.9261 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.6116 3.0626 1.0738 H 0 0 0 0 0 0 0 0 0\n 2.3036 3.3399 0.6292 H 0 0 0 0 0 0 0 0 0\n 5.3046 0.0000 0.7896 H 0 0 0 0 0 0 0 0 0\n 5.7670 0.0184 2.5009 H 0 0 0 0 0 0 0 0 0\n 6.4576 1.2122 1.3851 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 2 8 1 0 0 0\n 3 6 1 0 0 0\n 3 7 2 0 0 0\n 4 9 1 0 0 0\n 4 10 1 0 0 0\n 4 11 1 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 6 15 1 0 0 0\n 6 16 1 0 0 0\n 6 17 1 0 0 0\nM END\n"), + new TestChemical( + "Kohlentetrachlorid", + "Kohlenstoffgruppe oder Kohlenstoff-Silicium-Gruppe bezeichnet die 4. Hauptgruppe („Tetrele“) (nach neuer Nummerierung der IUPAC Gruppe 14) des Periodensystems. Sie umfasst die Elemente Kohlenstoff (C), Silicium (Si), Germanium (Ge), Zinn (Sn) und Blei (Pb). Auch ein radioaktives Element, das Flerovium (Fl), ist vertreten.", + "Methane, tetrachloro-; Benzinoform; Carbon chloride (CCl4); Carbona; Fasciolin; Flukoids; Freon 10; Necatorina; Perchloromethane; Tetrachlorocarbon; Tetrachloromethane; Tetrafinol; Tetraform; Tetrasol; Univerm; Vermoestricid; CCl4; Benzenoform; Carbon tet; Methane tetrachloride; Czterochlorek wegla; ENT 4,705; Halon 1040; Necatorine; R 10; Tetrachloorkoolstof; Tetrachloormetaan; Tetrachlorkohlenstoff, tetra; Tetrachlormethan; Tetrachlorure de carbone; Tetraclorometano; Tetracloruro di carbonio; Chlorid uhlicity; ENT 27164; Rcra waste number U211; UN 1846; Katharin; Seretin; Thawpit; NSC 97063; R 10 (Refrigerant)", + "CCl4", + 153.823m, + "56-23-5", + "\n\n\n 5 4 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.0000 C 0000000000000000000\n 1.0350 1.0350 1.0350 Cl 0000000000000000000\n -1.0350 -1.0350 1.0350 Cl 0000000000000000000\n -1.0350 1.0350 -1.0350 Cl 0000000000000000000\n 1.0350 -1.0350 -1.0350 Cl 0000000000000000000\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), + new TestChemical( + "Chloroform", + "Chloroform (systematische Bezeichnung Trichlormethan) ist ein chlorierter Kohlenwasserstoff mit der Summenformel CHCl3.", + "Chloroform; Freon 20; Methane, trichloro-; R 20; Trichloroform; CHCl3; Formyl trichloride; Methane trichloride; Methenyl trichloride; Methyl trichloride; Chloroforme; Cloroformio; NCI-C02686; R 20 (refrigerant); Trichloormethaan; Trichlormethan; Triclorometano; Rcra waste number U044; UN 1888; NSC 77361; F 20", + "CHCl3", + 119.378m, + "67-66-3", + "\n\n\n 5 4 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.4548 C 0000000000000000000\n 0.0000 0.0000 1.5402 H 0000000000000000000\n 0.0000 1.7050 -0.0837 Cl 0000000000000000000\n 1.4766 -0.8525 -0.0837 Cl 0000000000000000000\n -1.4766 -0.8525 -0.0837 Cl 0000000000000000000\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), + new TestChemical( + "Cyclohexan", + "Cyclohexan (auch Hexahydrobenzol, Hexamethylen, Naphthen) ist eine farblose Flüssigkeit. Es ist ein Cycloalkan mit der Summenformel C6H12, das im Erdöl vorkommt und als Lösungsmittel und Grundstoff in der Synthese genutzt wird.", + "Benzene, hexahydro-; Hexahydrobenzene; Hexamethylene; Hexanaphthene; Cicloesano; Cykloheksan; Rcra waste number U056; UN 1145; NSC 406835", + "C6H12", + 84.1595m, + "110-82-7", + "\n\n\n 18 18 0 0 0 0 0 0 0 0 V2000\n 0.0000 -1.4672 0.2293 C 0000000000000000000\n -1.2706 0.7336 0.2293 C 0000000000000000000\n 1.2706 0.7336 0.2293 C 0000000000000000000\n 0.0000 1.4672 -0.2293 C 0000000000000000000\n -1.2706 -0.7336 -0.2293 C 0000000000000000000\n 1.2706 -0.7336 -0.2293 C 0000000000000000000\n 0.0000 -1.5355 1.3276 H 0000000000000000000\n -1.3298 0.7677 1.3276 H 0000000000000000000\n 1.3298 0.7677 1.3276 H 0000000000000000000\n 0.0000 1.5355 -1.3276 H 0000000000000000000\n -1.3298 -0.7677 -1.3276 H 0000000000000000000\n 1.3298 -0.7677 -1.3276 H 0000000000000000000\n 0.0000 -2.4985 -0.1466 H 0000000000000000000\n -2.1638 1.2493 -0.1466 H 0000000000000000000\n 2.1638 1.2493 -0.1466 H 0000000000000000000\n 0.0000 2.4985 0.1466 H 0000000000000000000\n -2.1638 -1.2493 0.1466 H 0000000000000000000\n 2.1638 -1.2493 0.1466 H 0000000000000000000\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 1 13 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 2 8 1 0 0 0\n 2 14 1 0 0 0\n 3 4 1 0 0 0\n 3 6 1 0 0 0\n 3 9 1 0 0 0\n 3 15 1 0 0 0\n 4 10 1 0 0 0\n 4 16 1 0 0 0\n 5 11 1 0 0 0\n 5 17 1 0 0 0\n 6 12 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), + new TestChemical( + "Cyclopenten", + "Cyclopenten ist eine organische Verbindung mit der Summenformel C5H8. Sie besteht aus einem fünfgliedrigen, ungesättigten Ring, welcher eine Doppelbindung aufweist. In der homologen Reihe der Cycloalkene steht Cyclopenten zwischen Cyclobuten und Cyclohexen. Formal handelt es sich um ein einfach hydriertes Cyclopentadien beziehungsweise ein einfach dehydriertes Cyclopentan. Cyclopenten besitzt nur wenige Anwendungen.", + "", + "C5H8", + 68.1170m, + "142-29-0", + "\n\n\n 13 13 0 0 0 1 V2000\n 0.7026 2.1487 1.2803 C 0 0 0 0 0 0 0 0 0\n 1.9143 3.0520 1.0028 C 0 0 0 0 0 0 0 0 0\n 3.1024 2.1512 1.1173 C 0 0 0 0 0 0 0 0 0\n 2.7505 0.8940 1.4098 C 0 0 0 0 0 0 0 0 0\n 1.2685 0.7440 1.5398 C 0 0 0 0 0 0 0 0 0\n 0.1217 2.5129 2.1461 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.1470 0.4281 H 0 0 0 0 0 0 0 0 0\n 1.8684 3.5149 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.9848 3.8877 1.7230 H 0 0 0 0 0 0 0 0 0\n 4.1143 2.5269 0.9699 H 0 0 0 0 0 0 0 0 0\n 3.4198 0.0455 1.5471 H 0 0 0 0 0 0 0 0 0\n 0.8848 0.0000 0.8178 H 0 0 0 0 0 0 0 0 0\n 0.9989 0.3651 2.5427 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 5 1 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 4 2 0 0 0\n 3 10 1 0 0 0\n 4 5 1 0 0 0\n 4 11 1 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\nM END\n"), + new TestChemical( + "Diethylether", + "Diethylether ist der wichtigste Vertreter der organisch-chemischen Verbindungsklasse der Ether und wird deshalb häufig auch einfach als Ether (standardsprachlich und in der älteren wissenschaftlichen Literatur Äther) bezeichnet. Aufgrund der Herstellung aus Ethanol und Schwefelsäure war die historische Bezeichnung Schwefeläther, obwohl Diethylether keinen Schwefel enthält.", + "Ethane, 1,1'-oxybis-; Anaesthetic ether; Anesthesia ether; Anesthetic ether; Diethyl ether; Diethyl oxide; Ethoxyethane; Pronarcol; Solvent ether; 1,1'-Oxybisethane; (C2H5)2O; Aether; Diaethylaether; Dwuetylowy eter; Etere etilico; Ether ethylique; Ether, ethyl; Ethyl ether, tech.; Ethyl oxide; Oxyde d'ethyle; Rcra waste number U117; UN 1155; 3-Oxapentane; Ether; Ethyl ether anhydrous A.C.S.; Sulfuric ether; NSC 100036", + "C4H10O", + 74.1216m, + "60-29-7", + "\n\n\n 15 14 0 0 0 1 V2000\n 0.9744 2.7710 2.3574 C 0 0 0 0 0 0 0 0 0\n 0.9176 4.1589 1.7420 C 0 0 0 0 0 0 0 0 0\n 1.8106 1.8731 1.6596 O 0 0 0 0 0 0 0 0 0\n 3.2030 2.0811 1.8614 C 0 0 0 0 0 0 0 0 0\n 3.9340 1.0107 1.0734 C 0 0 0 0 0 0 0 0 0\n 0.0000 2.2572 2.2827 H 0 0 0 0 0 0 0 0 0\n 1.2519 2.8230 3.4284 H 0 0 0 0 0 0 0 0 0\n 0.1388 4.7597 2.2281 H 0 0 0 0 0 0 0 0 0\n 0.6868 4.1218 0.6694 H 0 0 0 0 0 0 0 0 0\n 1.8666 4.7007 1.8520 H 0 0 0 0 0 0 0 0 0\n 3.4420 2.0182 2.9412 H 0 0 0 0 0 0 0 0 0\n 3.4890 3.0953 1.5205 H 0 0 0 0 0 0 0 0 0\n 5.0187 1.1245 1.1931 H 0 0 0 0 0 0 0 0 0\n 3.7104 1.0671 0.0000 H 0 0 0 0 0 0 0 0 0\n 3.6667 0.0000 1.4086 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 4 1 0 0 0\n 4 5 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 5 15 1 0 0 0\nM END\n"), + new TestChemical( + "Ethanol", + "Ethanol (häufige Trivialnamen: Äthanol, Ethylalkohol, Alkohol) ist ein aliphatischer, einwertiger Alkohol mit der Summenformel C2H6O. Die reine Substanz ist eine bei Raumtemperatur farblose, leicht entzündliche Flüssigkeit mit einem brennenden Geschmack und einem charakteristischen, würzigen (süßlichen) Geruch. Die als Lebergift eingestufte Droge wird bei der Herstellung von Genussmitteln und alkoholischen Getränken wie Wein, Bier und Spirituosen aus kohlehydrathaltigem Material durch eine von Hefen ausgelöste Gärung in relativ großen Mengen produziert.", + "Ethyl alcohol; Alcohol; Alcohol anhydrous; Algrain; Anhydrol; Denatured ethanol; Ethyl hydrate; Ethyl hydroxide; Jaysol; Jaysol S; Methylcarbinol; SD Alchol 23-hydrogen; Tecsol; C2H5OH; Absolute ethanol; Cologne spirit; Fermentation alcohol; Grain alcohol; Molasses alcohol; Potato alcohol; Aethanol; Aethylalkohol; Alcohol, dehydrated; Alcool ethylique; Alcool etilico; Alkohol; Cologne spirits; Denatured alcohol CD-10; Denatured alcohol CD-5; Denatured alcohol CD-5a; Denatured alcohol SD-1; Denatured alcohol SD-13a; Denatured alcohol SD-17; Denatured alcohol SD-23a; Denatured alcohol SD-28; Denatured alcohol SD-3a; Denatured alcohol SD-30; Denatured alcohol SD-39b; Denatured alcohol SD-39c; Denatured alcohol SD-40m; Etanolo; Ethanol 200 proof; Ethyl alc; Etylowy alkohol; EtOH; NCI-C03134; Spirits of wine; Spirt; Alkoholu etylowego; Ethyl alcohol anhydrous; SD alcohol 23-hydrogen; UN 1170; Tecsol C; Alcare Hand Degermer; Absolute alcohol; Denatured alcohol; Ethanol, silent spirit; Ethylol; Punctilious ethyl alcohol; SD 3A", + "C2H6O", + 46.0684m, + "64-17-5", + "\n\n\n 9 8 0 0 0 0 0 0 0 0999 V2000\n 1.0195 0.8856 0.9752 C 0 0 0 0 0\n 1.8780 1.9882 1.5739 C 0 0 0 0 0\n 3.1989 1.4758 1.7291 O 0 0 0 0 0\n -0.0045 1.2392 0.8098 H 0 0 0 0 0\n 0.9875 0.0188 1.6438 H 0 0 0 0 0\n 1.4360 0.5612 0.0153 H 0 0 0 0 0\n 1.8717 2.8699 0.9114 H 0 0 0 0 0\n 1.4594 2.3055 2.5439 H 0 0 0 0 0\n 3.7472 2.1776 2.1115 H 0 0 0 0 0\n 6 1 1 0 0 0\n 4 1 1 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 7 2 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\nM END\n"), + new TestChemical( + "Essigsäureethylester", + "Essigsäureethylester, auch Ethylacetat oder Essigester, ist eine chemische Verbindung aus der Gruppe der Carbonsäureester. Es ist der Ester von Essigsäure und Ethanol. Die farblose Flüssigkeit ist ein charakteristisch nach Klebstoff riechendes Lösungsmittel, das in der chemischen Industrie und in Laboratorien oft verwendet wird.", + "Acetic acid, ethyl ester; Acetic ether; Acetidin; Acetoxyethane; Ethyl acetic ester; Ethyl ethanoate; Vinegar naphtha; CH3COOC2H5; Aethylacetat; Essigester; Ethyle (acetate d'); Etile (acetato di); Ethylacetaat; Ethylester kyseliny octove; Rcra waste number U112; UN 1173; Ethyl ester of acetic acid; 1-Acetoxyethane; NSC 70930; ac. acetic ethyl ester", + "C4H8O2", + 88.1051m, + "141-78-6", + "\n\n\n 14 13 0 0 0 0 0 0 0 0999 V2000\n 3.4731 2.2227 2.4832 O 0 0 0 0 0\n 2.1749 2.8339 2.4803 C 0 0 0 0 0\n 2.2477 4.1592 1.7469 C 0 0 0 0 0\n 3.7187 1.3350 1.4827 C 0 0 0 0 0\n 5.1346 0.8483 1.5545 C 0 0 0 0 0\n 2.9285 1.0057 0.6092 O 0 0 0 0 0\n 1.4145 2.1783 2.0421 H 0 0 0 0 0\n 1.8932 3.0019 3.5247 H 0 0 0 0 0\n 1.2769 4.6623 1.7528 H 0 0 0 0 0\n 2.9878 4.8162 2.2162 H 0 0 0 0 0\n 2.5643 4.0145 0.7089 H 0 0 0 0 0\n 5.1634 -0.2191 1.3199 H 0 0 0 0 0\n 5.7451 1.4089 0.8424 H 0 0 0 0 0\n 5.5400 0.9802 2.5620 H 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 5 1 0 0 0\n 4 6 2 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\nM END\n"), + new TestChemical( + "Ethylpropylether", + "Ethylpropylether", + "Ether, ethyl propyl; Ethyl n-propyl ether; Ethyl propyl ether; Propyl ethyl ether; 1-Ethoxypropane; n-C3H7OC2H5; UN 2615", + "C5H12O", + 88.1482m, + "628-32-0", + "\n\n\n 18 17 0 0 0 1 V2000\n 3.1079 1.7452 1.0645 O 0 0 0 0 0 0 0 0 0\n 4.4441 1.2694 1.1114 C 0 0 0 0 0 0 0 0 0\n 5.3490 2.4893 1.2632 C 0 0 0 0 0 0 0 0 0\n 2.1504 0.7063 0.9251 C 0 0 0 0 0 0 0 0 0\n 6.8002 2.0698 1.3208 C 0 0 0 0 0 0 0 0 0\n 0.7827 1.3612 0.8832 C 0 0 0 0 0 0 0 0 0\n 4.6830 0.7077 0.1862 H 0 0 0 0 0 0 0 0 0\n 4.5718 0.5698 1.9616 H 0 0 0 0 0 0 0 0 0\n 5.0789 3.0544 2.1773 H 0 0 0 0 0 0 0 0 0\n 5.1881 3.1923 0.4219 H 0 0 0 0 0 0 0 0 0\n 2.2307 0.0000 1.7749 H 0 0 0 0 0 0 0 0 0\n 2.3473 0.1294 0.0000 H 0 0 0 0 0 0 0 0 0\n 7.4601 2.9403 1.4280 H 0 0 0 0 0 0 0 0 0\n 7.1058 1.5371 0.4105 H 0 0 0 0 0 0 0 0 0\n 6.9969 1.4029 2.1707 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.5997 0.7766 H 0 0 0 0 0 0 0 0 0\n 0.6863 2.0571 0.0395 H 0 0 0 0 0 0 0 0 0\n 0.5712 1.9289 1.7988 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 5 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 4 6 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 5 15 1 0 0 0\n 6 16 1 0 0 0\n 6 17 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), + new TestChemical( + "Essigsäureisopropylester", + "Essigsäureisopropylester ist eine chemische Verbindung aus der Gruppe der Carbonsäureester. Es ist eine farblose, leichtentzündliche und flüchtige Flüssigkeit.", + "Acetic acid, 1-methylethyl ester; Acetic acid, isopropyl ester; 2-Acetoxypropane; 2-Propyl acetate; CH3COOCH(CH3)2; Acetate d'isopropyle; Isopropile(acetato di); Isopropyl ethanoate; Isopropyl (acetate d'); Isopropylacetaat; Isopropylacetat; Isopropylester kyseliny octove; UN 1220; Isopropyl ester of acetic acid; sec-Propyl acetate; Acetic acid, 2-propyl ester; 1-Methylethyl acetate; NSC 9295", + "C5H10O2", + 102.1317m, + "108-21-4", + "\n\n\n 17 16 0 0 0 1 V2000\n 3.1938 1.0106 1.8174 O 0 0 0 0 0 0 0 0 0\n 2.0014 1.7632 2.1082 C 0 0 0 0 0 0 0 0 0\n 4.4164 1.5950 1.9641 C 0 0 0 0 0 0 0 0 0\n 0.9499 0.7230 2.4807 C 0 0 0 0 0 0 0 0 0\n 1.5706 2.5640 0.8846 C 0 0 0 0 0 0 0 0 0\n 5.5443 0.6574 1.6359 C 0 0 0 0 0 0 0 0 0\n 4.5054 2.7501 2.3330 O 0 0 0 0 0 0 0 0 0\n 2.1755 2.4482 2.9734 H 0 0 0 0 0 0 0 0 0\n 1.2515 0.1324 3.3552 H 0 0 0 0 0 0 0 0 0\n 0.7564 0.0192 1.6598 H 0 0 0 0 0 0 0 0 0\n 0.0000 1.2159 2.7240 H 0 0 0 0 0 0 0 0 0\n 1.4415 1.9261 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.6116 3.0626 1.0738 H 0 0 0 0 0 0 0 0 0\n 2.3036 3.3399 0.6292 H 0 0 0 0 0 0 0 0 0\n 5.3046 0.0000 0.7896 H 0 0 0 0 0 0 0 0 0\n 5.7670 0.0184 2.5009 H 0 0 0 0 0 0 0 0 0\n 6.4576 1.2122 1.3851 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 2 8 1 0 0 0\n 3 6 1 0 0 0\n 3 7 2 0 0 0\n 4 9 1 0 0 0\n 4 10 1 0 0 0\n 4 11 1 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 6 15 1 0 0 0\n 6 16 1 0 0 0\n 6 17 1 0 0 0\nM END\n"), new TestChemical( "Methan", "Methan ist eine chemische Verbindung aus der Gruppe der Alkane mit der Summenformel CH4. Das farb- und geruchlose, brennbare Gas kommt in der Natur vor und ist ein Hauptbestandteil von Erdgas. Es dient als Heizgas und ist in der chemischen Industrie als Ausgangsprodukt für technische Synthesen von großer Bedeutung.", - "Marsh gas; Methyl hydride; CH4; Fire Damp; R 50; Biogas; R 50 (refrigerant)", "CH4", 16.0425m, "74-82-8", + "Marsh gas; Methyl hydride; CH4; Fire Damp; R 50; Biogas; R 50 (refrigerant)", + "CH4", + 16.0425m, + "74-82-8", "\n\n\n 5 4 0 0 0 1 V2000\n 1.0582 0.9353 0.8103 C 0 0 0 0 0 0 0 0 0\n 1.4145 1.5662 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.2065 1.4452 1.7588 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.7294 0.6710 H 0 0 0 0 0 0 0 0 0\n 1.6121 0.0000 0.8114 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), - new TestChemical("Methylalkohol", - "Methanol, auch Methylalkohol, ist eine organische chemische Verbindung mit der Summenformel CH4O (Halbstrukturformel: CH3OH) und der einfachste Vertreter aus der Stoffgruppe der Alkohole. Unter Normalbedingungen ist Methanol eine klare, farblose, entzündliche und leicht flüchtige Flüssigkeit mit alkoholischem Geruch.", - "Methanol; Carbinol; Methyl hydroxide; Methylol; Monohydroxymethane; Wood alcohol; CH3OH; Colonial spirit; Columbian spirit; Hydroxymethane; Wood naphtha; Alcool methylique; Alcool metilico; Columbian spirits; Metanolo; Methylalkohol; Metylowy alkohol; Pyroxylic spirit; Wood spirit; Rcra waste number U154; UN 1230; Pyro alcohol; Spirit of wood; Bieleski's solution; NSC 85232", - "CH4O", 32.0419m, "67-56-1", - "\n\n\n 6 5 0 0 0 1 V2000\n 0.2453 0.8386 1.6056 H 0 0 0 0 0 0 0 0 0\n 0.6776 0.9803 0.6074 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.5869 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.8133 0.0000 0.1338 H 0 0 0 0 0 0 0 0 0\n 1.8631 1.7142 0.6464 O 0 0 0 0 0 0 0 0 0\n 2.4856 1.2216 1.1660 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 2 3 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 5 6 1 0 0 0\nM END\n"), - new TestChemical("2-Butanone", - "Butanon ist neben Aceton eines der wichtigsten industriell genutzten Ketone. Es ist eine farblose, leicht bewegliche Flüssigkeit mit einem typischen Geruch und wird allgemein als Methylethylketon (MEK) bezeichnet.", - "Butan-2-one; Butanone; Ethyl methyl ketone; Ketone, methyl ethyl; Methyl ethyl ketone; MEK; C2H5COCH3; Acetone, methyl-; Aethylmethylketon; 3-Butanone; Butanone 2; Ethyl methyl cetone; Ethylmethylketon; Ketone, ethyl methyl; Meetco; Methyl acetone; Metiletilchetone; Metyloetyloketon; Rcra waste number U159; UN 1193; 2-Oxobutane; 2-Butanal; 2-butanone (MEK; methyl ethyl ketone); 2-butanone (MEK)", - "C4H8O", 72.1057m, "78-93-3", - "\n\n\n 13 12 0 0 0 1 V2000\n 1.7893 1.4200 2.8269 C 0 0 0 0 0 0 0 0 0\n 0.8451 2.5827 2.6164 C 0 0 0 0 0 0 0 0 0\n 2.3705 0.9490 1.5071 C 0 0 0 0 0 0 0 0 0\n 3.5557 1.7224 0.9969 C 0 0 0 0 0 0 0 0 0\n 1.9075 0.0000 0.9038 O 0 0 0 0 0 0 0 0 0\n 2.6155 1.7113 3.5049 H 0 0 0 0 0 0 0 0 0\n 1.2589 0.5906 3.3355 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.3072 1.9722 H 0 0 0 0 0 0 0 0 0\n 0.4300 2.9289 3.5716 H 0 0 0 0 0 0 0 0 0\n 1.3499 3.4390 2.1488 H 0 0 0 0 0 0 0 0 0\n 3.8802 1.3974 0.0000 H 0 0 0 0 0 0 0 0 0\n 3.3200 2.7937 0.9410 H 0 0 0 0 0 0 0 0 0\n 4.4074 1.6050 1.6799 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 4 1 0 0 0\n 3 5 2 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\nM END\n"), + new TestChemical( + "Methylalkohol", + "Methanol, auch Methylalkohol, ist eine organische chemische Verbindung mit der Summenformel CH4O (Halbstrukturformel: CH3OH) und der einfachste Vertreter aus der Stoffgruppe der Alkohole. Unter Normalbedingungen ist Methanol eine klare, farblose, entzündliche und leicht flüchtige Flüssigkeit mit alkoholischem Geruch.", + "Methanol; Carbinol; Methyl hydroxide; Methylol; Monohydroxymethane; Wood alcohol; CH3OH; Colonial spirit; Columbian spirit; Hydroxymethane; Wood naphtha; Alcool methylique; Alcool metilico; Columbian spirits; Metanolo; Methylalkohol; Metylowy alkohol; Pyroxylic spirit; Wood spirit; Rcra waste number U154; UN 1230; Pyro alcohol; Spirit of wood; Bieleski's solution; NSC 85232", + "CH4O", + 32.0419m, + "67-56-1", + "\n\n\n 6 5 0 0 0 1 V2000\n 0.2453 0.8386 1.6056 H 0 0 0 0 0 0 0 0 0\n 0.6776 0.9803 0.6074 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.5869 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.8133 0.0000 0.1338 H 0 0 0 0 0 0 0 0 0\n 1.8631 1.7142 0.6464 O 0 0 0 0 0 0 0 0 0\n 2.4856 1.2216 1.1660 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 2 3 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 5 6 1 0 0 0\nM END\n"), + new TestChemical( + "2-Butanone", + "Butanon ist neben Aceton eines der wichtigsten industriell genutzten Ketone. Es ist eine farblose, leicht bewegliche Flüssigkeit mit einem typischen Geruch und wird allgemein als Methylethylketon (MEK) bezeichnet.", + "Butan-2-one; Butanone; Ethyl methyl ketone; Ketone, methyl ethyl; Methyl ethyl ketone; MEK; C2H5COCH3; Acetone, methyl-; Aethylmethylketon; 3-Butanone; Butanone 2; Ethyl methyl cetone; Ethylmethylketon; Ketone, ethyl methyl; Meetco; Methyl acetone; Metiletilchetone; Metyloetyloketon; Rcra waste number U159; UN 1193; 2-Oxobutane; 2-Butanal; 2-butanone (MEK; methyl ethyl ketone); 2-butanone (MEK)", + "C4H8O", + 72.1057m, + "78-93-3", + "\n\n\n 13 12 0 0 0 1 V2000\n 1.7893 1.4200 2.8269 C 0 0 0 0 0 0 0 0 0\n 0.8451 2.5827 2.6164 C 0 0 0 0 0 0 0 0 0\n 2.3705 0.9490 1.5071 C 0 0 0 0 0 0 0 0 0\n 3.5557 1.7224 0.9969 C 0 0 0 0 0 0 0 0 0\n 1.9075 0.0000 0.9038 O 0 0 0 0 0 0 0 0 0\n 2.6155 1.7113 3.5049 H 0 0 0 0 0 0 0 0 0\n 1.2589 0.5906 3.3355 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.3072 1.9722 H 0 0 0 0 0 0 0 0 0\n 0.4300 2.9289 3.5716 H 0 0 0 0 0 0 0 0 0\n 1.3499 3.4390 2.1488 H 0 0 0 0 0 0 0 0 0\n 3.8802 1.3974 0.0000 H 0 0 0 0 0 0 0 0 0\n 3.3200 2.7937 0.9410 H 0 0 0 0 0 0 0 0 0\n 4.4074 1.6050 1.6799 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 4 1 0 0 0\n 3 5 2 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\nM END\n"), new TestChemical( "Butan", "Die Butane sind eine Stoffgruppe innerhalb der Alkane, die die Summenformel C4H10 aufweisen. Sie besteht aus den beiden Vertretern n-Butan und iso-Butan, die zueinander isomer sind. Beide Butane sind farblose, brennbare, leicht zu verflüssigende Gase („Flüssiggase“), die sich kaum in Wasser, aber gut in Ethanol und Ether lösen.", "n-Butane; Diethyl; Freon 600; Liquefied petroleum gas; LPG; n-C4H10; Butanen; Butani; Methylethylmethane; UN 1011; A 21; HC 600; HC 600 (hydrocarbon); R 600; R 600 (alkane)", - "C4H10", 58.1222m, "106-97-8", + "C4H10", + 58.1222m, + "106-97-8", "\n\n\n 14 13 0 0 0 1 V2000\n 3.5864 1.1360 0.9321 C 0 0 0 0 0 0 0 0 0\n 2.5594 0.8276 1.9979 C 0 0 0 0 0 0 0 0 0\n 1.7180 2.0488 2.3336 C 0 0 0 0 0 0 0 0 0\n 0.6912 1.7404 3.3995 C 0 0 0 0 0 0 0 0 0\n 3.1136 1.4730 0.0000 H 0 0 0 0 0 0 0 0 0\n 4.1901 0.2515 0.6922 H 0 0 0 0 0 0 0 0 0\n 4.2769 1.9280 1.2514 H 0 0 0 0 0 0 0 0 0\n 3.0625 0.4552 2.9124 H 0 0 0 0 0 0 0 0 0\n 1.9030 0.0000 1.6631 H 0 0 0 0 0 0 0 0 0\n 1.2148 2.4207 1.4190 H 0 0 0 0 0 0 0 0 0\n 2.3747 2.8766 2.6678 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.9495 3.0791 H 0 0 0 0 0 0 0 0 0\n 0.0880 2.6250 3.6404 H 0 0 0 0 0 0 0 0 0\n 1.1635 1.4017 4.3313 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 4 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\nM END\n"), - new TestChemical("Butanol", - "1-Butanol (auch n-Butanol oder nach IUPAC Butan-1-ol) ist eine chemische Verbindung aus der Gruppe der Alkanole. Der primäre Alkohol leitet sich vom aliphatischen Kohlenwasserstoff n-Butan ab.", - "Butyl alcohol; n-Butan-1-ol; n-Butanol; n-Butyl alcohol; Butyl hydroxide; CCS 203; Hemostyp; Methylolpropane; Propylcarbinol; n-C4H9OH; Butanol; Butan-1-ol; 1-Hydroxybutane; Alcool butylique; Butanolo; Butylowy alkohol; Butyric alcohol; Propylmethanol; Butanolen; 1-Butyl alcohol; Rcra waste number U031; Butanol-1; NSC 62782", - "C4H10O", 74.1216m, "71-36-3", - "\n\n\n 15 14 0 0 0 1 V2000\n 2.9651 2.0464 2.4042 C 0 0 0 0 0 0 0 0 0\n 2.3281 2.7103 1.1934 C 0 0 0 0 0 0 0 0 0\n 0.8652 2.3158 0.9860 C 0 0 0 0 0 0 0 0 0\n 3.3109 0.5952 2.1559 C 0 0 0 0 0 0 0 0 0\n 0.0000 2.7697 1.9922 O 0 0 0 0 0 0 0 0 0\n 3.8836 2.6000 2.6826 H 0 0 0 0 0 0 0 0 0\n 2.2886 2.1307 3.2779 H 0 0 0 0 0 0 0 0 0\n 2.8987 2.4514 0.2796 H 0 0 0 0 0 0 0 0 0\n 2.4074 3.8120 1.2923 H 0 0 0 0 0 0 0 0 0\n 0.5089 2.6739 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.7244 1.2206 1.0177 H 0 0 0 0 0 0 0 0 0\n 2.4210 0.0000 1.9086 H 0 0 0 0 0 0 0 0 0\n 3.7712 0.1403 3.0423 H 0 0 0 0 0 0 0 0 0\n 4.0193 0.4827 1.3240 H 0 0 0 0 0 0 0 0 0\n 0.1229 3.7075 2.0732 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 5 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\nM END\n"), - new TestChemical("Heptan", "Heptane sind zu den Alkanen zählende Kohlenwasserstoffe mit der Summenformel C7H16. Es existieren neun Konstitutionsisomere", - "n-Heptane; Dipropylmethane; Heptyl hydride; Skellysolve C; n-C7H16; Eptani; Heptan; Heptanen; Gettysolve-C; NSC 62784", "C7H16", 100.2019m, - "142-82-5", - "\n\n\n 23 22 0 0 0 1 V2000\n 4.9119 1.1117 1.6160 C 0 0 0 0 0 0 0 0 0\n 3.6978 1.8855 1.1285 C 0 0 0 0 0 0 0 0 0\n 2.4040 1.2047 1.5468 C 0 0 0 0 0 0 0 0 0\n 6.2065 1.7895 1.1965 C 0 0 0 0 0 0 0 0 0\n 1.1708 1.9589 1.0725 C 0 0 0 0 0 0 0 0 0\n 7.4159 1.0214 1.6802 C 0 0 0 0 0 0 0 0 0\n 0.9041 3.2087 1.8814 C 0 0 0 0 0 0 0 0 0\n 4.8843 0.0753 1.2230 H 0 0 0 0 0 0 0 0 0\n 4.8794 1.0110 2.7196 H 0 0 0 0 0 0 0 0 0\n 3.7266 2.9218 1.5233 H 0 0 0 0 0 0 0 0 0\n 3.7322 1.9883 0.0250 H 0 0 0 0 0 0 0 0 0\n 2.3839 0.1736 1.1402 H 0 0 0 0 0 0 0 0 0\n 2.3759 1.0904 2.6492 H 0 0 0 0 0 0 0 0 0\n 6.2340 2.8252 1.5899 H 0 0 0 0 0 0 0 0 0\n 6.2388 1.8907 0.0934 H 0 0 0 0 0 0 0 0 0\n 1.2758 2.2178 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.2900 1.2893 1.1316 H 0 0 0 0 0 0 0 0 0\n 7.4323 0.9394 2.7752 H 0 0 0 0 0 0 0 0 0\n 8.3484 1.5122 1.3742 H 0 0 0 0 0 0 0 0 0\n 7.4348 0.0000 1.2773 H 0 0 0 0 0 0 0 0 0\n 1.7349 3.9244 1.8126 H 0 0 0 0 0 0 0 0 0\n 0.7607 2.9796 2.9459 H 0 0 0 0 0 0 0 0 0\n 0.0000 3.7234 1.5317 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 8 1 0 0 0\n 1 9 1 0 0 0\n 2 3 1 0 0 0\n 2 10 1 0 0 0\n 2 11 1 0 0 0\n 3 5 1 0 0 0\n 3 12 1 0 0 0\n 3 13 1 0 0 0\n 4 6 1 0 0 0\n 4 14 1 0 0 0\n 4 15 1 0 0 0\n 5 7 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\nM END\n"), + new TestChemical( + "Butanol", + "1-Butanol (auch n-Butanol oder nach IUPAC Butan-1-ol) ist eine chemische Verbindung aus der Gruppe der Alkanole. Der primäre Alkohol leitet sich vom aliphatischen Kohlenwasserstoff n-Butan ab.", + "Butyl alcohol; n-Butan-1-ol; n-Butanol; n-Butyl alcohol; Butyl hydroxide; CCS 203; Hemostyp; Methylolpropane; Propylcarbinol; n-C4H9OH; Butanol; Butan-1-ol; 1-Hydroxybutane; Alcool butylique; Butanolo; Butylowy alkohol; Butyric alcohol; Propylmethanol; Butanolen; 1-Butyl alcohol; Rcra waste number U031; Butanol-1; NSC 62782", + "C4H10O", + 74.1216m, + "71-36-3", + "\n\n\n 15 14 0 0 0 1 V2000\n 2.9651 2.0464 2.4042 C 0 0 0 0 0 0 0 0 0\n 2.3281 2.7103 1.1934 C 0 0 0 0 0 0 0 0 0\n 0.8652 2.3158 0.9860 C 0 0 0 0 0 0 0 0 0\n 3.3109 0.5952 2.1559 C 0 0 0 0 0 0 0 0 0\n 0.0000 2.7697 1.9922 O 0 0 0 0 0 0 0 0 0\n 3.8836 2.6000 2.6826 H 0 0 0 0 0 0 0 0 0\n 2.2886 2.1307 3.2779 H 0 0 0 0 0 0 0 0 0\n 2.8987 2.4514 0.2796 H 0 0 0 0 0 0 0 0 0\n 2.4074 3.8120 1.2923 H 0 0 0 0 0 0 0 0 0\n 0.5089 2.6739 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.7244 1.2206 1.0177 H 0 0 0 0 0 0 0 0 0\n 2.4210 0.0000 1.9086 H 0 0 0 0 0 0 0 0 0\n 3.7712 0.1403 3.0423 H 0 0 0 0 0 0 0 0 0\n 4.0193 0.4827 1.3240 H 0 0 0 0 0 0 0 0 0\n 0.1229 3.7075 2.0732 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 5 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\nM END\n"), + new TestChemical( + "Heptan", + "Heptane sind zu den Alkanen zählende Kohlenwasserstoffe mit der Summenformel C7H16. Es existieren neun Konstitutionsisomere", + "n-Heptane; Dipropylmethane; Heptyl hydride; Skellysolve C; n-C7H16; Eptani; Heptan; Heptanen; Gettysolve-C; NSC 62784", + "C7H16", + 100.2019m, + "142-82-5", + "\n\n\n 23 22 0 0 0 1 V2000\n 4.9119 1.1117 1.6160 C 0 0 0 0 0 0 0 0 0\n 3.6978 1.8855 1.1285 C 0 0 0 0 0 0 0 0 0\n 2.4040 1.2047 1.5468 C 0 0 0 0 0 0 0 0 0\n 6.2065 1.7895 1.1965 C 0 0 0 0 0 0 0 0 0\n 1.1708 1.9589 1.0725 C 0 0 0 0 0 0 0 0 0\n 7.4159 1.0214 1.6802 C 0 0 0 0 0 0 0 0 0\n 0.9041 3.2087 1.8814 C 0 0 0 0 0 0 0 0 0\n 4.8843 0.0753 1.2230 H 0 0 0 0 0 0 0 0 0\n 4.8794 1.0110 2.7196 H 0 0 0 0 0 0 0 0 0\n 3.7266 2.9218 1.5233 H 0 0 0 0 0 0 0 0 0\n 3.7322 1.9883 0.0250 H 0 0 0 0 0 0 0 0 0\n 2.3839 0.1736 1.1402 H 0 0 0 0 0 0 0 0 0\n 2.3759 1.0904 2.6492 H 0 0 0 0 0 0 0 0 0\n 6.2340 2.8252 1.5899 H 0 0 0 0 0 0 0 0 0\n 6.2388 1.8907 0.0934 H 0 0 0 0 0 0 0 0 0\n 1.2758 2.2178 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.2900 1.2893 1.1316 H 0 0 0 0 0 0 0 0 0\n 7.4323 0.9394 2.7752 H 0 0 0 0 0 0 0 0 0\n 8.3484 1.5122 1.3742 H 0 0 0 0 0 0 0 0 0\n 7.4348 0.0000 1.2773 H 0 0 0 0 0 0 0 0 0\n 1.7349 3.9244 1.8126 H 0 0 0 0 0 0 0 0 0\n 0.7607 2.9796 2.9459 H 0 0 0 0 0 0 0 0 0\n 0.0000 3.7234 1.5317 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 8 1 0 0 0\n 1 9 1 0 0 0\n 2 3 1 0 0 0\n 2 10 1 0 0 0\n 2 11 1 0 0 0\n 3 5 1 0 0 0\n 3 12 1 0 0 0\n 3 13 1 0 0 0\n 4 6 1 0 0 0\n 4 14 1 0 0 0\n 4 15 1 0 0 0\n 5 7 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\nM END\n"), new TestChemical( "Hexan", "n-Hexan ist eine den Alkanen (gesättigte Kohlenwasserstoffe) zugehörige chemische Verbindung. Es ist eine farblose Flüssigkeit mit der Summenformel C6H14. Es ist das unverzweigte Isomer der fünf Hexanisomeren.", - "n-Hexane; Skellysolve B; n-C6H14; Esani; Heksan; Hexanen; Hexyl hydride; Gettysolve-B; NCI-C60571; NSC 68472", "C6H14", 86.1754m, "110-54-3", + "n-Hexane; Skellysolve B; n-C6H14; Esani; Heksan; Hexanen; Hexyl hydride; Gettysolve-B; NCI-C60571; NSC 68472", + "C6H14", + 86.1754m, + "110-54-3", "\n\n\n 20 19 0 0 0 1 V2000\n 3.0831 1.0831 1.9494 C 0 0 0 0 0 0 0 0 0\n 2.9735 1.4738 3.4178 C 0 0 0 0 0 0 0 0 0\n 3.7734 2.7246 3.7510 C 0 0 0 0 0 0 0 0 0\n 2.1289 1.8691 1.0623 C 0 0 0 0 0 0 0 0 0\n 5.2567 2.5130 3.5423 C 0 0 0 0 0 0 0 0 0\n 0.6830 1.5862 1.4060 C 0 0 0 0 0 0 0 0 0\n 2.8829 0.0000 1.8355 H 0 0 0 0 0 0 0 0 0\n 4.1330 1.2389 1.6114 H 0 0 0 0 0 0 0 0 0\n 1.9001 1.6293 3.6713 H 0 0 0 0 0 0 0 0 0\n 3.3194 0.6360 4.0538 H 0 0 0 0 0 0 0 0 0\n 3.4222 3.5715 3.1275 H 0 0 0 0 0 0 0 0 0\n 3.5761 3.0236 4.7988 H 0 0 0 0 0 0 0 0 0\n 2.3340 2.9542 1.1601 H 0 0 0 0 0 0 0 0 0\n 2.3218 1.6234 0.0000 H 0 0 0 0 0 0 0 0 0\n 5.8188 3.4487 3.6460 H 0 0 0 0 0 0 0 0 0\n 5.6708 1.7956 4.2625 H 0 0 0 0 0 0 0 0 0\n 5.4489 2.1109 2.5328 H 0 0 0 0 0 0 0 0 0\n 0.5108 1.7246 2.4871 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.2530 0.8669 H 0 0 0 0 0 0 0 0 0\n 0.4010 0.5540 1.1613 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 7 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 5 1 0 0 0\n 3 11 1 0 0 0\n 3 12 1 0 0 0\n 4 6 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\nM END\n"), new TestChemical( "Octan", "n-Octan ist eine farblose Flüssigkeit, die zu den Alkanen zählt. In der Chemie wird es entsprechend den aktuellen Nomenklaturregeln als n-Octan geschrieben, in Deutschland wird jedoch oft – gerade im Zusammenhang mit der Oktanzahl – die veraltete Schreibweise Oktan bevorzugt. Es handelt sich um den unverzweigten Vertreter der 18 Isomere der Octane.", - "n-Octane; n-C8H18; Oktan; Oktanen; Ottani; UN 1262", "C8H18", 114.2285m, "111-65-9", + "n-Octane; n-C8H18; Oktan; Oktanen; Ottani; UN 1262", + "C8H18", + 114.2285m, + "111-65-9", "\n\n\n 26 25 0 0 0 1 V2000\n 3.8144 2.3469 1.6808 C 0 0 0 0 0 0 0 0 0\n 2.8536 2.9952 2.6656 C 0 0 0 0 0 0 0 0 0\n 1.4042 2.8035 2.2422 C 0 0 0 0 0 0 0 0 0\n 5.2606 2.6768 2.0091 C 0 0 0 0 0 0 0 0 0\n 1.0820 3.5021 0.9282 C 0 0 0 0 0 0 0 0 0\n 6.2381 2.0228 1.0442 C 0 0 0 0 0 0 0 0 0\n 0.8478 4.9859 1.0983 C 0 0 0 0 0 0 0 0 0\n 6.4020 0.5402 1.2955 C 0 0 0 0 0 0 0 0 0\n 3.5588 2.6881 0.6508 H 0 0 0 0 0 0 0 0 0\n 3.6652 1.2480 1.6733 H 0 0 0 0 0 0 0 0 0\n 3.0793 4.0774 2.7579 H 0 0 0 0 0 0 0 0 0\n 3.0069 2.5713 3.6775 H 0 0 0 0 0 0 0 0 0\n 0.7310 3.1711 3.0415 H 0 0 0 0 0 0 0 0 0\n 1.1916 1.7202 2.1456 H 0 0 0 0 0 0 0 0 0\n 5.4911 2.3686 3.0487 H 0 0 0 0 0 0 0 0 0\n 5.4015 3.7762 1.9864 H 0 0 0 0 0 0 0 0 0\n 0.1878 3.0372 0.4697 H 0 0 0 0 0 0 0 0 0\n 1.9182 3.3248 0.2134 H 0 0 0 0 0 0 0 0 0\n 7.2242 2.5203 1.1318 H 0 0 0 0 0 0 0 0 0\n 5.9096 2.1958 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.6299 5.4668 0.1362 H 0 0 0 0 0 0 0 0 0\n 0.0000 5.1885 1.7663 H 0 0 0 0 0 0 0 0 0\n 1.7255 5.4904 1.5249 H 0 0 0 0 0 0 0 0 0\n 5.4519 0.0000 1.1826 H 0 0 0 0 0 0 0 0 0\n 7.1172 0.0943 0.5924 H 0 0 0 0 0 0 0 0 0\n 6.7707 0.3401 2.3104 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 9 1 0 0 0\n 1 10 1 0 0 0\n 2 3 1 0 0 0\n 2 11 1 0 0 0\n 2 12 1 0 0 0\n 3 5 1 0 0 0\n 3 13 1 0 0 0\n 3 14 1 0 0 0\n 4 6 1 0 0 0\n 4 15 1 0 0 0\n 4 16 1 0 0 0\n 5 7 1 0 0 0\n 5 17 1 0 0 0\n 5 18 1 0 0 0\n 6 8 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\n 8 24 1 0 0 0\n 8 25 1 0 0 0\n 8 26 1 0 0 0\nM END\n"), new TestChemical( "Pentan", "Pentane sind Kohlenwasserstoffe mit der Summenformel C5H12 und zählen zu den Alkanen. Es existieren drei Konstitutionsisomere: n-Pentan, Isopentan und Neopentan.", - "n-Pentane; Skellysolve A; n-C5H12; Pentan; Pentanen; Pentani; Amyl hydride; NSC 72415", "C5H12", 72.1488m, "109-66-0", + "n-Pentane; Skellysolve A; n-C5H12; Pentan; Pentanen; Pentani; Amyl hydride; NSC 72415", + "C5H12", + 72.1488m, + "109-66-0", "\n\n\n 17 16 0 0 0 1 V2000\n 3.7280 2.4135 2.8751 C 0 0 0 0 0 0 0 0 0\n 2.5997 1.4666 2.4988 C 0 0 0 0 0 0 0 0 0\n 1.8538 1.9510 1.2659 C 0 0 0 0 0 0 0 0 0\n 4.4698 1.9317 4.1013 C 0 0 0 0 0 0 0 0 0\n 0.7349 1.0066 0.8889 C 0 0 0 0 0 0 0 0 0\n 3.3234 3.4299 3.0526 H 0 0 0 0 0 0 0 0 0\n 4.4308 2.5184 2.0246 H 0 0 0 0 0 0 0 0 0\n 1.8961 1.3604 3.3490 H 0 0 0 0 0 0 0 0 0\n 3.0039 0.4497 2.3206 H 0 0 0 0 0 0 0 0 0\n 2.5589 2.0599 0.4178 H 0 0 0 0 0 0 0 0 0\n 1.4479 2.9664 1.4462 H 0 0 0 0 0 0 0 0 0\n 5.2821 2.6183 4.3713 H 0 0 0 0 0 0 0 0 0\n 3.8049 1.8514 4.9716 H 0 0 0 0 0 0 0 0 0\n 4.9169 0.9415 3.9412 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.9059 1.6988 H 0 0 0 0 0 0 0 0 0\n 1.1125 0.0000 0.6654 H 0 0 0 0 0 0 0 0 0\n 0.1970 1.3600 0.0000 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 5 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\nM END\n"), - new TestChemical("1-Pentanol", - "1-Pentanol (veraltet: Amylalkohol) ist eine organische chemische Verbindung und gehört zu den Alkoholen. 1-Pentanol ist Bestandteil der Fuselöle.", - "Pentyl alcohol; n-Amyl alcohol; n-Butylcarbinol; n-Pentan-1-ol; n-Pentanol; n-Pentyl alcohol; Amyl alcohol; Amylol; Pentanol; 1-Pentyl alcohol; n-C5H11OH; Pentan-1-ol; Pentanol-1; Pentasol; n-Amylalkohol; Alcool amylique; Amyl alcohol, n-; Amyl alcohol, normal; Primary amyl alcohol; UN 1105; 1-Pentol; Primary-N-amyl alcohol; Butyl carbinol; NSC 5707", - "C5H12O", 88.1482m, "71-41-0", - "\n\n\n 18 17 0 0 0 1 V2000\n 2.0264 2.2581 1.9056 C 0 0 0 0 0 0 0 0 0\n 3.2462 1.6488 1.2359 C 0 0 0 0 0 0 0 0 0\n 4.4464 1.6425 2.1693 C 0 0 0 0 0 0 0 0 0\n 0.8116 2.2939 0.9786 C 0 0 0 0 0 0 0 0 0\n 5.6613 1.0380 1.5026 C 0 0 0 0 0 0 0 0 0\n 0.3196 1.0290 0.6256 O 0 0 0 0 0 0 0 0 0\n 2.2542 3.2923 2.2314 H 0 0 0 0 0 0 0 0 0\n 1.7856 1.7003 2.8338 H 0 0 0 0 0 0 0 0 0\n 3.0191 0.6161 0.9030 H 0 0 0 0 0 0 0 0 0\n 3.4892 2.2109 0.3117 H 0 0 0 0 0 0 0 0 0\n 4.6718 2.6757 2.5011 H 0 0 0 0 0 0 0 0 0\n 4.2030 1.0807 3.0931 H 0 0 0 0 0 0 0 0 0\n 1.0538 2.7449 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.8939 1.4351 H 0 0 0 0 0 0 0 0 0\n 5.4791 0.0000 1.1934 H 0 0 0 0 0 0 0 0 0\n 5.9510 1.5990 0.6041 H 0 0 0 0 0 0 0 0 0\n 6.5258 1.0323 2.1788 H 0 0 0 0 0 0 0 0 0\n 0.1634 0.5491 1.4297 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 7 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 5 1 0 0 0\n 3 11 1 0 0 0\n 3 12 1 0 0 0\n 4 6 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), - new TestChemical("1-Propanol", - "Propanole sind Alkohole mit drei Kohlenstoffatomen und einer Hydroxygruppe (–OH). Sie haben die allgemeine Summenformel C3H8O und eine molare Masse von 60,10 g/mol. Es gibt nur zwei Isomere.", - "Propyl alcohol; n-Propan-1-ol; n-Propanol; n-Propyl alcohol; Ethylcarbinol; Optal; Osmosol extra; Propanol; Propylic alcohol; 1-Propyl alcohol; n-C3H7OH; 1-Hydroxypropane; Propanol-1; Propan-1-ol; n-Propyl alkohol; Alcool propilico; Alcool propylique; Propanole; Propanolen; Propanoli; Propylowy alkohol; UN 1274; Propylan-propyl alcohol; NSC 30300; Alcohol, propyl", - "C3H8O", 60.0950m, "71-23-8", - "\n\n\n 12 11 0 0 0 1 V2000\n 0.7713 1.5705 1.3838 C 0 0 0 0 0 0 0 0 0\n 2.1696 1.0226 1.0958 C 0 0 0 0 0 0 0 0 0\n 3.2631 1.9141 1.6398 C 0 0 0 0 0 0 0 0 0\n 0.3563 1.3950 2.7118 O 0 0 0 0 0 0 0 0 0\n 0.7013 2.6399 1.1017 H 0 0 0 0 0 0 0 0 0\n 0.0000 1.0213 0.8161 H 0 0 0 0 0 0 0 0 0\n 2.2872 0.9154 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.2716 0.0000 1.5099 H 0 0 0 0 0 0 0 0 0\n 3.2248 1.9921 2.7352 H 0 0 0 0 0 0 0 0 0\n 3.1932 2.9340 1.2381 H 0 0 0 0 0 0 0 0 0\n 4.2564 1.5270 1.3793 H 0 0 0 0 0 0 0 0 0\n 1.0138 1.7939 3.2691 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\nM END\n"), + new TestChemical( + "1-Pentanol", + "1-Pentanol (veraltet: Amylalkohol) ist eine organische chemische Verbindung und gehört zu den Alkoholen. 1-Pentanol ist Bestandteil der Fuselöle.", + "Pentyl alcohol; n-Amyl alcohol; n-Butylcarbinol; n-Pentan-1-ol; n-Pentanol; n-Pentyl alcohol; Amyl alcohol; Amylol; Pentanol; 1-Pentyl alcohol; n-C5H11OH; Pentan-1-ol; Pentanol-1; Pentasol; n-Amylalkohol; Alcool amylique; Amyl alcohol, n-; Amyl alcohol, normal; Primary amyl alcohol; UN 1105; 1-Pentol; Primary-N-amyl alcohol; Butyl carbinol; NSC 5707", + "C5H12O", + 88.1482m, + "71-41-0", + "\n\n\n 18 17 0 0 0 1 V2000\n 2.0264 2.2581 1.9056 C 0 0 0 0 0 0 0 0 0\n 3.2462 1.6488 1.2359 C 0 0 0 0 0 0 0 0 0\n 4.4464 1.6425 2.1693 C 0 0 0 0 0 0 0 0 0\n 0.8116 2.2939 0.9786 C 0 0 0 0 0 0 0 0 0\n 5.6613 1.0380 1.5026 C 0 0 0 0 0 0 0 0 0\n 0.3196 1.0290 0.6256 O 0 0 0 0 0 0 0 0 0\n 2.2542 3.2923 2.2314 H 0 0 0 0 0 0 0 0 0\n 1.7856 1.7003 2.8338 H 0 0 0 0 0 0 0 0 0\n 3.0191 0.6161 0.9030 H 0 0 0 0 0 0 0 0 0\n 3.4892 2.2109 0.3117 H 0 0 0 0 0 0 0 0 0\n 4.6718 2.6757 2.5011 H 0 0 0 0 0 0 0 0 0\n 4.2030 1.0807 3.0931 H 0 0 0 0 0 0 0 0 0\n 1.0538 2.7449 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.8939 1.4351 H 0 0 0 0 0 0 0 0 0\n 5.4791 0.0000 1.1934 H 0 0 0 0 0 0 0 0 0\n 5.9510 1.5990 0.6041 H 0 0 0 0 0 0 0 0 0\n 6.5258 1.0323 2.1788 H 0 0 0 0 0 0 0 0 0\n 0.1634 0.5491 1.4297 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 7 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 5 1 0 0 0\n 3 11 1 0 0 0\n 3 12 1 0 0 0\n 4 6 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), + new TestChemical( + "1-Propanol", + "Propanole sind Alkohole mit drei Kohlenstoffatomen und einer Hydroxygruppe (–OH). Sie haben die allgemeine Summenformel C3H8O und eine molare Masse von 60,10 g/mol. Es gibt nur zwei Isomere.", + "Propyl alcohol; n-Propan-1-ol; n-Propanol; n-Propyl alcohol; Ethylcarbinol; Optal; Osmosol extra; Propanol; Propylic alcohol; 1-Propyl alcohol; n-C3H7OH; 1-Hydroxypropane; Propanol-1; Propan-1-ol; n-Propyl alkohol; Alcool propilico; Alcool propylique; Propanole; Propanolen; Propanoli; Propylowy alkohol; UN 1274; Propylan-propyl alcohol; NSC 30300; Alcohol, propyl", + "C3H8O", + 60.0950m, + "71-23-8", + "\n\n\n 12 11 0 0 0 1 V2000\n 0.7713 1.5705 1.3838 C 0 0 0 0 0 0 0 0 0\n 2.1696 1.0226 1.0958 C 0 0 0 0 0 0 0 0 0\n 3.2631 1.9141 1.6398 C 0 0 0 0 0 0 0 0 0\n 0.3563 1.3950 2.7118 O 0 0 0 0 0 0 0 0 0\n 0.7013 2.6399 1.1017 H 0 0 0 0 0 0 0 0 0\n 0.0000 1.0213 0.8161 H 0 0 0 0 0 0 0 0 0\n 2.2872 0.9154 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.2716 0.0000 1.5099 H 0 0 0 0 0 0 0 0 0\n 3.2248 1.9921 2.7352 H 0 0 0 0 0 0 0 0 0\n 3.1932 2.9340 1.2381 H 0 0 0 0 0 0 0 0 0\n 4.2564 1.5270 1.3793 H 0 0 0 0 0 0 0 0 0\n 1.0138 1.7939 3.2691 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\nM END\n"), new TestChemical( "Phenol", "Phenol (nach IUPAC: Benzenol, veraltet: Karbolsäure oder kurz Karbol) ist eine aromatische, organische Verbindung und besteht aus einer Phenylgruppe (–C6H5), an die eine Hydroxygruppe (–OH) gebunden ist. Der farblose, kristalline Feststoff ist eine wichtige Industriechemikalie und dient als Zwischenprodukt besonders zur Herstellung diverser Kunststoffe.", "Carbolic acid; Baker's P and S Liquid and Ointment; Benzenol; Hydroxybenzene; Izal; Monohydroxybenzene; Monophenol; Oxybenzene; Phenic acid; Phenyl alcohol; Phenyl hydrate; Phenyl hydroxide; Phenylic acid; Phenylic alcohol; PhOH; Benzene, hydroxy-; Acide carbolique; Baker's P & S liquid & Ointment; Fenol; Fenolo; NCI-C50124; Paoscle; Phenole; Carbolsaure; NA 2821; Phenol alcohol; Phenol, molten; Rcra waste number U188; UN 1671; UN 2312; UN 2821; Phenic alcohol; NSC 36808", - "C6H6O", 94.1112m, "108-95-2", + "C6H6O", + 94.1112m, + "108-95-2", "\n\n\n 13 13 0 0 0 1 V2000\n 0.3792 2.3991 0.0767 O 0 0 0 0 0 0 0 0 0\n 1.7410 2.2635 0.0604 C 0 0 0 0 0 0 0 0 0\n 2.4822 3.4537 0.0746 C 0 0 0 0 0 0 0 0 0\n 2.3800 1.0169 0.0335 C 0 0 0 0 0 0 0 0 0\n 3.8685 3.3780 0.0622 C 0 0 0 0 0 0 0 0 0\n 3.7689 0.9695 0.0214 C 0 0 0 0 0 0 0 0 0\n 4.5128 2.1443 0.0358 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.5292 0.0598 H 0 0 0 0 0 0 0 0 0\n 1.9740 4.4239 0.0950 H 0 0 0 0 0 0 0 0 0\n 1.8001 0.0870 0.0221 H 0 0 0 0 0 0 0 0 0\n 4.4597 4.2998 0.0734 H 0 0 0 0 0 0 0 0 0\n 4.2770 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0\n 5.6063 2.0990 0.0278 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 8 1 0 0 0\n 2 3 2 0 0 0\n 4 2 1 0 0 0\n 3 5 1 0 0 0\n 3 9 1 0 0 0\n 6 4 2 0 0 0\n 4 10 1 0 0 0\n 5 7 2 0 0 0\n 5 11 1 0 0 0\n 7 6 1 0 0 0\n 6 12 1 0 0 0\n 7 13 1 0 0 0\nM END\n"), new TestChemical( - "Propan", "Propan ist ein farbloses brennbares Gas und gehört zu den Kohlenwasserstoffen. Es steht in der homologen Reihe der Alkane an dritter Stelle.", - "n-Propane; Dimethylmethane; Freon 290; Liquefied petroleum gas; LPG; Propyl hydride; R 290; C3H8; UN 1978; A-108; Hydrocarbon propellant A-108; HC 290", "C3H8", - 44.0956m, "74-98-6", + "Propan", + "Propan ist ein farbloses brennbares Gas und gehört zu den Kohlenwasserstoffen. Es steht in der homologen Reihe der Alkane an dritter Stelle.", + "n-Propane; Dimethylmethane; Freon 290; Liquefied petroleum gas; LPG; Propyl hydride; R 290; C3H8; UN 1978; A-108; Hydrocarbon propellant A-108; HC 290", + "C3H8", + 44.0956m, + "74-98-6", "\n\n\n 11 10 0 0 0 1 V2000\n 3.3461 1.6436 1.3326 C 0 0 0 0 0 0 0 0 0\n 2.0042 1.0740 0.9307 C 0 0 0 0 0 0 0 0 0\n 0.9734 1.2486 2.0232 C 0 0 0 0 0 0 0 0 0\n 3.7393 1.1540 2.2333 H 0 0 0 0 0 0 0 0 0\n 3.2805 2.7182 1.5491 H 0 0 0 0 0 0 0 0 0\n 4.0907 1.5138 0.5370 H 0 0 0 0 0 0 0 0 0\n 1.6524 1.5612 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.1104 0.0000 0.6811 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.8391 1.7246 H 0 0 0 0 0 0 0 0 0\n 0.8224 2.3078 2.2707 H 0 0 0 0 0 0 0 0 0\n 1.2746 0.7385 2.9479 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\nM END\n"), - new TestChemical("p-Xylen", "", - "Benzene, 1,4-dimethyl-; p-Dimethylbenzene; p-Xylol; 1,4-Dimethylbenzene; 1,4-Xylene; p-Methyltoluene; para-Xylene; Chromar; Scintillar; 4-Methyltoluene; NSC 72419; 1,4-dimethyl-benzene ( p-xylene)", - "C8H10", 106.1650m, "106-42-3", - "\n\n\n 18 18 0 0 0 1 V2000\n 1.0336 0.8636 0.7240 C 0 0 0 0 0 0 0 0 0\n 1.8222 1.2612 1.9184 C 0 0 0 0 0 0 0 0 0\n 3.2175 1.2383 1.8736 C 0 0 0 0 0 0 0 0 0\n 1.1814 1.6695 3.0884 C 0 0 0 0 0 0 0 0 0\n 3.9602 1.6220 2.9826 C 0 0 0 0 0 0 0 0 0\n 1.9245 2.0535 4.1976 C 0 0 0 0 0 0 0 0 0\n 3.3192 2.0345 4.1521 C 0 0 0 0 0 0 0 0 0\n 4.1123 2.4606 5.3336 C 0 0 0 0 0 0 0 0 0\n 0.9917 1.6889 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.5972 0.9816 H 0 0 0 0 0 0 0 0 0\n 1.4818 0.0000 0.2149 H 0 0 0 0 0 0 0 0 0\n 3.7265 0.9144 0.9590 H 0 0 0 0 0 0 0 0 0\n 0.0866 1.6867 3.1337 H 0 0 0 0 0 0 0 0 0\n 5.0549 1.5997 2.9402 H 0 0 0 0 0 0 0 0 0\n 1.4139 2.3716 5.1134 H 0 0 0 0 0 0 0 0 0\n 5.0993 1.9796 5.3595 H 0 0 0 0 0 0 0 0 0\n 3.6010 2.2196 6.2750 H 0 0 0 0 0 0 0 0 0\n 4.2743 3.5470 5.3142 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 9 1 0 0 0\n 1 10 1 0 0 0\n 1 11 1 0 0 0\n 3 2 2 0 0 0\n 2 4 1 0 0 0\n 5 3 1 0 0 0\n 3 12 1 0 0 0\n 4 6 2 0 0 0\n 4 13 1 0 0 0\n 7 5 2 0 0 0\n 5 14 1 0 0 0\n 6 7 1 0 0 0\n 6 15 1 0 0 0\n 7 8 1 0 0 0\n 8 16 1 0 0 0\n 8 17 1 0 0 0\n 8 18 1 0 0 0\nM END\n"), + new TestChemical( + "p-Xylen", + "", + "Benzene, 1,4-dimethyl-; p-Dimethylbenzene; p-Xylol; 1,4-Dimethylbenzene; 1,4-Xylene; p-Methyltoluene; para-Xylene; Chromar; Scintillar; 4-Methyltoluene; NSC 72419; 1,4-dimethyl-benzene ( p-xylene)", + "C8H10", + 106.1650m, + "106-42-3", + "\n\n\n 18 18 0 0 0 1 V2000\n 1.0336 0.8636 0.7240 C 0 0 0 0 0 0 0 0 0\n 1.8222 1.2612 1.9184 C 0 0 0 0 0 0 0 0 0\n 3.2175 1.2383 1.8736 C 0 0 0 0 0 0 0 0 0\n 1.1814 1.6695 3.0884 C 0 0 0 0 0 0 0 0 0\n 3.9602 1.6220 2.9826 C 0 0 0 0 0 0 0 0 0\n 1.9245 2.0535 4.1976 C 0 0 0 0 0 0 0 0 0\n 3.3192 2.0345 4.1521 C 0 0 0 0 0 0 0 0 0\n 4.1123 2.4606 5.3336 C 0 0 0 0 0 0 0 0 0\n 0.9917 1.6889 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.5972 0.9816 H 0 0 0 0 0 0 0 0 0\n 1.4818 0.0000 0.2149 H 0 0 0 0 0 0 0 0 0\n 3.7265 0.9144 0.9590 H 0 0 0 0 0 0 0 0 0\n 0.0866 1.6867 3.1337 H 0 0 0 0 0 0 0 0 0\n 5.0549 1.5997 2.9402 H 0 0 0 0 0 0 0 0 0\n 1.4139 2.3716 5.1134 H 0 0 0 0 0 0 0 0 0\n 5.0993 1.9796 5.3595 H 0 0 0 0 0 0 0 0 0\n 3.6010 2.2196 6.2750 H 0 0 0 0 0 0 0 0 0\n 4.2743 3.5470 5.3142 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 9 1 0 0 0\n 1 10 1 0 0 0\n 1 11 1 0 0 0\n 3 2 2 0 0 0\n 2 4 1 0 0 0\n 5 3 1 0 0 0\n 3 12 1 0 0 0\n 4 6 2 0 0 0\n 4 13 1 0 0 0\n 7 5 2 0 0 0\n 5 14 1 0 0 0\n 6 7 1 0 0 0\n 6 15 1 0 0 0\n 7 8 1 0 0 0\n 8 16 1 0 0 0\n 8 17 1 0 0 0\n 8 18 1 0 0 0\nM END\n"), new TestChemical( "Toluol", "Toluol, Trivialname nach IUPAC auch Toluen, Methylbenzol, Phenylmethan, nach IUPAC-Nomenklatur Methylbenzen genannt, ist eine farblose, charakteristisch riechende, flüchtige Flüssigkeit, die in vielen ihrer Eigenschaften dem Benzol ähnelt. Toluol ist ein aromatischer Kohlenwasserstoff, häufig ersetzt es als Lösungsmittel das giftige Benzol. Es ist unter anderem auch im Benzin enthalten.", "Benzene, methyl; Methacide; Methylbenzene; Methylbenzol; Phenylmethane; Antisal 1a; Toluol; Methane, phenyl-; NCI-C07272; Tolueen; Toluen; Toluolo; Rcra waste number U220; Tolu-sol; UN 1294; Dracyl; Monomethyl benzene; CP 25; NSC 406333; methylbenzene (toluene)", - "C7H8", 92.1384m, "108-88-3", + "C7H8", + 92.1384m, + "108-88-3", "\n\n\n 7 7 0 0 0 1 V2000\n 1.4722 1.7260 0.0000 C 0 0 0 0 0 0 0 0 0\n 0.9645 0.8630 0.0000 C 0 0 0 0 0 0 0 0 0\n 2.4874 1.7260 0.0000 C 0 0 0 0 0 0 0 0 0\n 2.9951 0.8630 0.0000 C 0 0 0 0 0 0 0 0 0\n 2.4874 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0\n 1.4722 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0\n 0.0000 0.8630 0.0000 C 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0\n 3 1 1 0 0 0\n 2 6 1 0 0 0\n 2 7 1 0 0 0\n 4 3 2 0 0 0\n 5 4 1 0 0 0\n 6 5 2 0 0 0\nM END\n"), new TestChemical( "Wasser", "Wasser (H2O) ist eine chemische Verbindung aus den Elementen Sauerstoff (O) und Wasserstoff (H). Wasser ist als Flüssigkeit durchsichtig, weitgehend farb-, geruch- und geschmacklos. Wasser ist die einzige chemische Verbindung auf der Erde, die in der Natur als Flüssigkeit, als Festkörper und als Gas vorkommt. Die Bezeichnung Wasser wird dabei für den flüssigen Aggregatzustand verwendet. Im festen Zustand spricht man von Eis, im gasförmigen Zustand von Wasserdampf. Wasser ist Grundlage des Lebens auf der Erde.", - "Water vapor; Distilled water; Ice; H2O; Dihydrogen oxide; steam; Tritiotope", "H2O", 18.0153m, "7732-18-5", + "Water vapor; Distilled water; Ice; H2O; Dihydrogen oxide; steam; Tritiotope", + "H2O", + 18.0153m, + "7732-18-5", "\n\n\n 3 2 0 0 0 0 0 0 0 0999 V2000\n -0.2308 -0.3260 0.0000 O 0 0 0 0 0\n 0.7373 -0.2766 0.0000 H 0 0 0 0 0\n -0.5064 0.6026 0.0000 H 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\nM END\n") }; } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/TestPerson.cs b/src/abstractions/Backend.Fx/RandomData/TestPerson.cs index 94886676..4e7ca0c7 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestPerson.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestPerson.cs @@ -13,77 +13,85 @@ public enum Genders Female } + private static readonly Dictionary InvalidCharacterReplacements = new Dictionary { - {"À", "A"}, - {"Á", "A"}, - {"Â", "A"}, - {"Ã", "A"}, - {"Ä", "A"}, - {"Å", "A"}, - {"Æ", "A"}, - {"Ç", "C"}, - {"È", "E"}, - {"É", "E"}, - {"Ê", "E"}, - {"Ë", "E"}, - {"Ì", "I"}, - {"Í", "I"}, - {"Î", "I"}, - {"Ï", "I"}, - {"Ð", "D"}, - {"Ñ", "N"}, - {"Ò", "O"}, - {"Ó", "O"}, - {"Ô", "O"}, - {"Õ", "O"}, - {"Ö", "O"}, - {"×", "x"}, - {"Ø", "O"}, - {"Ù", "U"}, - {"Ú", "U"}, - {"Û", "U"}, - {"Ü", "U"}, - {"Ý", "Y"}, - {"Þ", "p"}, - {"ß", "ss"}, - {"à", "a"}, - {"á", "a"}, - {"â", "a"}, - {"ã", "a"}, - {"ä", "a"}, - {"å", "a"}, - {"æ", "a"}, - {"ç", "c"}, - {"è", "e"}, - {"é", "e"}, - {"ê", "e"}, - {"ë", "e"}, - {"ì", "i"}, - {"í", "i"}, - {"î", "i"}, - {"ï", "i"}, - {"ð", "o"}, - {"ñ", "n"}, - {"ò", "o"}, - {"ó", "o"}, - {"ô", "o"}, - {"õ", "o"}, - {"ö", "o"}, - {"÷", ""}, - {"ø", "o"}, - {"ù", ""}, - {"ú", "u"}, - {"û", "u"}, - {"ü", "u"}, - {"ý", "y"}, - {"þ", "p"}, - {"ÿ", "y"} + { "À", "A" }, + { "Á", "A" }, + { "Â", "A" }, + { "Ã", "A" }, + { "Ä", "A" }, + { "Å", "A" }, + { "Æ", "A" }, + { "Ç", "C" }, + { "È", "E" }, + { "É", "E" }, + { "Ê", "E" }, + { "Ë", "E" }, + { "Ì", "I" }, + { "Í", "I" }, + { "Î", "I" }, + { "Ï", "I" }, + { "Ð", "D" }, + { "Ñ", "N" }, + { "Ò", "O" }, + { "Ó", "O" }, + { "Ô", "O" }, + { "Õ", "O" }, + { "Ö", "O" }, + { "×", "x" }, + { "Ø", "O" }, + { "Ù", "U" }, + { "Ú", "U" }, + { "Û", "U" }, + { "Ü", "U" }, + { "Ý", "Y" }, + { "Þ", "p" }, + { "ß", "ss" }, + { "à", "a" }, + { "á", "a" }, + { "â", "a" }, + { "ã", "a" }, + { "ä", "a" }, + { "å", "a" }, + { "æ", "a" }, + { "ç", "c" }, + { "è", "e" }, + { "é", "e" }, + { "ê", "e" }, + { "ë", "e" }, + { "ì", "i" }, + { "í", "i" }, + { "î", "i" }, + { "ï", "i" }, + { "ð", "o" }, + { "ñ", "n" }, + { "ò", "o" }, + { "ó", "o" }, + { "ô", "o" }, + { "õ", "o" }, + { "ö", "o" }, + { "÷", "" }, + { "ø", "o" }, + { "ù", "" }, + { "ú", "u" }, + { "û", "u" }, + { "ü", "u" }, + { "ý", "y" }, + { "þ", "p" }, + { "ÿ", "y" } }; private readonly string _email; - public TestPerson(string firstName, string middleName, string lastName, string title, Genders gender, DateTime dateOfBirth, string email = null) + public TestPerson( + string firstName, + string middleName, + string lastName, + string title, + Genders gender, + DateTime dateOfBirth, + string email = null) { _email = email; FirstName = firstName; @@ -106,9 +114,12 @@ public int Age { get { - DateTime today = DateTime.Today; - var age = today.Year - DateOfBirth.Year; - if (DateOfBirth > today.AddYears(-age)) age--; + var today = DateTime.Today; + int age = today.Year - DateOfBirth.Year; + if (DateOfBirth > today.AddYears(-age)) + { + age--; + } return age; } @@ -125,7 +136,11 @@ public int Age private static string SanitizeForUserName(string s) { s = new string(s.Where(char.IsLetterOrDigit).ToArray()); - foreach (var invalidCharacterReplacement in InvalidCharacterReplacements) s = s.Replace(invalidCharacterReplacement.Key, invalidCharacterReplacement.Value); + foreach (KeyValuePair invalidCharacterReplacement in InvalidCharacterReplacements) + { + s = s.Replace(invalidCharacterReplacement.Key, invalidCharacterReplacement.Value); + } + return s; } @@ -139,4 +154,4 @@ protected override IEnumerable GetEqualityComponents() yield return Title; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs index 2094a8dd..11be6687 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs @@ -11,8 +11,11 @@ public class TestPersonGenerator : Generator private readonly HashSet _uniqueNames = new HashSet(); public bool EnforceUniqueNames { get; set; } = false; + public int FemalePercentage { get; set; } = 55; + public int MaximumAgeInDays { get; set; } = 80 * Year; + public int MinimumAgeInDays { get; set; } = 18 * Year; public static TestPerson Generate() @@ -22,7 +25,7 @@ public static TestPerson Generate() protected override TestPerson Next() { - var isFemale = _random.Next(1, 100) < FemalePercentage; + bool isFemale = _random.Next(1, 100) < FemalePercentage; TestPerson generated; do { @@ -45,4 +48,4 @@ protected override TestPerson Next() return generated; } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs index a26a29d9..d1fb5313 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs @@ -9,7 +9,10 @@ public static class TestRandom public static IEnumerable Next(int amount, Func generate) { - for (var i = 0; i < amount; i++) yield return generate(); + for (var i = 0; i < amount; i++) + { + yield return generate(); + } } public static int Next() @@ -40,13 +43,13 @@ public static double NextDouble() public static DateTime RandomDateTime(int rangeDays) { return rangeDays < 0 - ? DateTime.Now.AddDays(-Next(-rangeDays)).AddSeconds(-Next(100000)) - : DateTime.Now.AddDays(Next(rangeDays)).AddSeconds(-Next(100000)); + ? DateTime.Now.AddDays(-Next(-rangeDays)).AddSeconds(-Next(100000)) + : DateTime.Now.AddDays(Next(rangeDays)).AddSeconds(-Next(100000)); } public static decimal RandomDecimal(int min = 0, int max = 999999) { - var abs = Next(min, max); + int abs = Next(min, max); return abs + Math.Round(Next(100) / 100m, 2); } @@ -60,12 +63,12 @@ public static string NextPassword(int length = 10) for (var i = 0; i < password.Length; i++) { - var rnd = Next(100); + int rnd = Next(100); password[i] = rnd < 60 - ? letters.Random() - : rnd < 90 - ? numbers.Random() - : specials.Random(); + ? letters.Random() + : rnd < 90 + ? numbers.Random() + : specials.Random(); } return new string(password); @@ -73,7 +76,7 @@ public static string NextPassword(int length = 10) public static decimal NextDecimal(decimal minimum, decimal maximum) { - return (decimal) Instance.NextDouble() * (maximum - minimum) + minimum; + return (decimal)Instance.NextDouble() * (maximum - minimum) + minimum; } public static bool NextProbability(int p) @@ -81,4 +84,4 @@ public static bool NextProbability(int p) return Instance.Next(0, 100) < p; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj b/src/environments/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj similarity index 70% rename from tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj rename to src/environments/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj index 5bef9165..6766952e 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj +++ b/src/environments/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj @@ -23,10 +23,9 @@ - - - - + + + diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Controllers/CalculationsController.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Controllers/CalculationsController.cs similarity index 96% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/Controllers/CalculationsController.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Controllers/CalculationsController.cs index d83525a6..b3e0a552 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Controllers/CalculationsController.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Controllers/CalculationsController.cs @@ -18,25 +18,23 @@ public IActionResult Addition(double arg1, double arg2) { return Ok(_calculationService.Add(arg1, arg2)); } - + [HttpPost("subtraction/{arg1}/{arg2}")] public IActionResult Subtraction(double arg1, double arg2) { return Ok(_calculationService.Subtract(arg1, arg2)); } - - + [HttpPost("multiplication/{arg1}/{arg2}")] public IActionResult Multiplication(double arg1, double arg2) { return Ok(_calculationService.Multiply(arg1, arg2)); } - - + [HttpPost("division/{arg1}/{arg2}")] public IActionResult Division(double arg1, double arg2) { return Ok(_calculationService.Divide(arg1, arg2)); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs similarity index 67% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs index 3e833eb7..200776bb 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs @@ -15,6 +15,7 @@ public interface ICalculationService CalculationResult Multiply(double arg1, double arg2); CalculationResult Divide(double arg1, double arg2); + public class CalculationResult { public CalculationResult(DateTime timestamp, string executor, double result, int tenantId) @@ -26,19 +27,26 @@ public CalculationResult(DateTime timestamp, string executor, double result, int } public DateTime Timestamp { get; } + public string Executor { get; } + public double Result { get; } + public int TenantId { get; } } } + public class CalculationService : ICalculationService, IDomainService { private readonly IClock _clock; private readonly ICurrentTHolder _identityHolder; private readonly ICurrentTHolder _tenantIdHolder; - public CalculationService(IClock clock, ICurrentTHolder identityHolder, ICurrentTHolder tenantIdHolder) + public CalculationService( + IClock clock, + ICurrentTHolder identityHolder, + ICurrentTHolder tenantIdHolder) { _clock = clock; _identityHolder = identityHolder; @@ -47,17 +55,29 @@ public CalculationService(IClock clock, ICurrentTHolder identityHolde public ICalculationService.CalculationResult Add(double arg1, double arg2) { - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 + arg2, _tenantIdHolder.Current.Value); + return new ICalculationService.CalculationResult( + _clock.UtcNow, + _identityHolder.Current.Name, + arg1 + arg2, + _tenantIdHolder.Current.Value); } public ICalculationService.CalculationResult Subtract(double arg1, double arg2) { - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 - arg2, _tenantIdHolder.Current.Value); + return new ICalculationService.CalculationResult( + _clock.UtcNow, + _identityHolder.Current.Name, + arg1 - arg2, + _tenantIdHolder.Current.Value); } public ICalculationService.CalculationResult Multiply(double arg1, double arg2) { - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 * arg2, _tenantIdHolder.Current.Value); + return new ICalculationService.CalculationResult( + _clock.UtcNow, + _identityHolder.Current.Name, + arg1 * arg2, + _tenantIdHolder.Current.Value); } public ICalculationService.CalculationResult Divide(double arg1, double arg2) @@ -67,9 +87,13 @@ public ICalculationService.CalculationResult Divide(double arg1, double arg2) throw new UnprocessableException("Attempt to divide by zero") .AddError("arg2", "Division by zero is not possible."); } - - - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 / arg2, _tenantIdHolder.Current.Value); + + + return new ICalculationService.CalculationResult( + _clock.UtcNow, + _identityHolder.Current.Name, + arg1 / arg2, + _tenantIdHolder.Current.Value); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs similarity index 73% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs index a56c80c8..48205476 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs @@ -15,12 +15,12 @@ public class JwtService public static string IssueJwt(string identityName) { var jwt = new JwtSecurityToken( - issuer: "SampleApp", - audience: "SampleApp", - claims: new[] {new Claim(ClaimTypes.Name, identityName)}, - notBefore: DateTime.UtcNow, - expires: DateTime.UtcNow.AddMinutes(5), - signingCredentials: SigningCredentials); + "SampleApp", + "SampleApp", + new[] { new Claim(ClaimTypes.Name, identityName) }, + DateTime.UtcNow, + DateTime.UtcNow.AddMinutes(5), + SigningCredentials); string jwtString = JwtSecurityTokenHandler.WriteToken(jwt); return jwtString; @@ -30,7 +30,7 @@ public static TokenValidationParameters TokenValidationParameters() { return new TokenValidationParameters { - NameClaimTypeRetriever = (token, s) => ClaimTypes.Name, + NameClaimTypeRetriever = (_, _) => ClaimTypes.Name, ValidateIssuerSigningKey = true, IssuerSigningKey = SigningCredentials.Key, ValidateIssuer = true, @@ -38,8 +38,8 @@ public static TokenValidationParameters TokenValidationParameters() ValidateAudience = true, ValidAudience = "SampleApp", ValidateLifetime = true, - ClockSkew = TimeSpan.FromMinutes(2), + ClockSkew = TimeSpan.FromMinutes(2) }; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/MultiTenantMiddleware.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/MultiTenantMiddleware.cs similarity index 90% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/MultiTenantMiddleware.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/MultiTenantMiddleware.cs index caad7fcf..cdbfb2fa 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/MultiTenantMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/MultiTenantMiddleware.cs @@ -1,7 +1,6 @@ using Backend.Fx.AspNetCore.MultiTenancy; using Backend.Fx.Environment.MultiTenancy; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; namespace Backend.Fx.AspNetCore.Tests.SampleApp { @@ -12,7 +11,7 @@ public MultiTenantMiddleware(RequestDelegate next) : base(next) protected override TenantId FindMatchingTenantId(HttpContext context) { - if (context.Request.Query.TryGetValue("tenantId", out StringValues tenantIdStr) + if (context.Request.Query.TryGetValue("tenantId", out var tenantIdStr) && int.TryParse(tenantIdStr[0], out int tenantId)) { return new TenantId(tenantId); @@ -21,4 +20,4 @@ protected override TenantId FindMatchingTenantId(HttpContext context) return new TenantId(null); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs similarity index 56% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs index ce2ca860..de146506 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs @@ -1,13 +1,13 @@ -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Extensions; using Backend.Fx.Logging; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.SimpleInjectorDependencyInjection; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; +using Microsoft.AspNetCore.Mvc; namespace Backend.Fx.AspNetCore.Tests.SampleApp.Runtime { @@ -17,19 +17,14 @@ public class SampleApplication : IBackendFxApplication public SampleApplication(ITenantIdProvider tenantIdProvider, IExceptionLogger exceptionLogger) { - Assembly domainAssembly = GetType().Assembly; - - _application = new BackendFxApplication( - new SimpleInjectorCompositionRoot(), - new InMemoryMessageBus(), - exceptionLogger); + var messageBus = new InMemoryMessageBus(); + var compositionRoot = new SimpleInjectorCompositionRoot(messageBus, GetType().Assembly); + _application = new BackendFxApplication(compositionRoot, messageBus, exceptionLogger); _application = new MultiTenantApplication(_application); - _application = new GenerateDataOnBoot( - tenantIdProvider, - new SimpleInjectorDataGenerationModule(domainAssembly), - _application); - _application.CompositionRoot.RegisterModules( - new SimpleInjectorDomainModule(domainAssembly)); + _application = new GenerateDataOnBoot(tenantIdProvider, _application); + + compositionRoot.Container.GetTypesToRegister(GetType().Assembly) + .ForAll(t => compositionRoot.Container.Register(t)); } public void Dispose() @@ -45,15 +40,9 @@ public void Dispose() public IMessageBus MessageBus => _application.MessageBus; - public bool WaitForBoot(int timeoutMilliSeconds = 2147483647, CancellationToken cancellationToken = default) - { - return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); public Task BootAsync(CancellationToken cancellationToken = default) { return _application.BootAsync(cancellationToken); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs similarity index 78% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs index 5a20a3ca..1ecc4d93 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs @@ -1,6 +1,5 @@ using Backend.Fx.AspNetCore.Tests.SampleApp.Runtime; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; @@ -9,14 +8,15 @@ namespace Backend.Fx.AspNetCore.Tests.SampleApp { public class SampleApplicationHostedService : BackendFxApplicationHostedService { - public ITenantService TenantService { get; } - public override IBackendFxApplication Application { get; } - public SampleApplicationHostedService(IExceptionLogger exceptionLogger) { IMessageBus messageBus = new InMemoryMessageBus(); TenantService = new TenantService(messageBus, new InMemoryTenantRepository()); - Application = new SampleApplication(TenantService.TenantIdProvider, exceptionLogger); + Application = new SampleApplication(new TenantServiceTenantIdProvider(TenantService), exceptionLogger); } + + public ITenantService TenantService { get; } + + public override IBackendFxApplication Application { get; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleJsonErrorHandlingMiddleware.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/SampleJsonErrorHandlingMiddleware.cs similarity index 78% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleJsonErrorHandlingMiddleware.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/SampleJsonErrorHandlingMiddleware.cs index def10e65..70053522 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleJsonErrorHandlingMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/SampleJsonErrorHandlingMiddleware.cs @@ -5,8 +5,7 @@ namespace Backend.Fx.AspNetCore.Tests.SampleApp { public class SampleJsonErrorHandlingMiddleware : JsonErrorHandlingMiddleware { - public SampleJsonErrorHandlingMiddleware(RequestDelegate next) : base(next, showInternalServerErrorDetails: true) - { - } + public SampleJsonErrorHandlingMiddleware(RequestDelegate next) : base(next, true) + { } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Startup.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Startup.cs similarity index 69% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/Startup.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Startup.cs index a16cf1d5..d347a2f8 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Startup.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/Startup.cs @@ -9,25 +9,28 @@ namespace Backend.Fx.AspNetCore.Tests.SampleApp { public class Startup { - private readonly ExceptionLoggers _exceptionLoggers = new (); + private readonly ExceptionLoggers _exceptionLoggers = new(); public void ConfigureServices(IServiceCollection services) { // diagnostics services services.AddSingleton(_exceptionLoggers); - + // decode jwt - services.AddAuthentication(authOpt => - { - authOpt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - authOpt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - authOpt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - }).AddJwtBearer(bearerOpt => bearerOpt.TokenValidationParameters = JwtService.TokenValidationParameters()); - - + services.AddAuthentication( + authOpt => + { + authOpt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + authOpt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + authOpt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer( + bearerOpt => bearerOpt.TokenValidationParameters = JwtService.TokenValidationParameters()); + + // enabling MVC services.AddMvc(); - + // integrate backend fx application as hosted service services.AddBackendFxApplication(); } @@ -36,24 +39,21 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // log exceptions to file _exceptionLoggers.Add(new ExceptionLogger(LogManager.Create("Mep.WebHost"))); - + // use the ASP.Net Core routing middleware that decides the endpoint to be hit later app.UseRouting(); - + // error handling: return rich JSON errors, app.UseMiddleware(); - + // decode JWT Bearer from Authorization header app.UseAuthentication(); app.UseMiddleware(); - + app.UseBackendFxApplication(); - - app.UseEndpoints(endpointRouteBuilder => - { - endpointRouteBuilder.MapControllers(); - }); + + app.UseEndpoints(endpointRouteBuilder => { endpointRouteBuilder.MapControllers(); }); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/TenantAdminMiddleware.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/TenantAdminMiddleware.cs similarity index 90% rename from tests/Backend.Fx.AspNetCore.Tests/SampleApp/TenantAdminMiddleware.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/TenantAdminMiddleware.cs index 627a3290..4d537d16 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/TenantAdminMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleApp/TenantAdminMiddleware.cs @@ -3,12 +3,11 @@ namespace Backend.Fx.AspNetCore.Tests.SampleApp { - public class TenantAdminMiddleware : TenantAdminMiddlewareBase + public class TenantAdminMiddleware : TenantAdminMiddlewareBase { - public TenantAdminMiddleware(RequestDelegate next, SampleApplicationHostedService hostedService) + public TenantAdminMiddleware(RequestDelegate next, SampleApplicationHostedService hostedService) : base(next, hostedService.TenantService) - { - } + { } protected override string GetTenantConfiguration(CreateTenantParams createTenantParams) { @@ -20,4 +19,4 @@ protected override bool IsTenantsAdmin(HttpContext context) return true; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleAppWebApplicationFactory.cs b/src/environments/Backend.Fx.AspNetCore.Tests/SampleAppWebApplicationFactory.cs similarity index 66% rename from tests/Backend.Fx.AspNetCore.Tests/SampleAppWebApplicationFactory.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/SampleAppWebApplicationFactory.cs index aa010b38..216707c8 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleAppWebApplicationFactory.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/SampleAppWebApplicationFactory.cs @@ -1,6 +1,5 @@ using Backend.Fx.AspNetCore.Tests.SampleApp; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.NetCore.Logging; +using Backend.Fx.Logging; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; @@ -21,12 +20,13 @@ protected override IWebHostBuilder CreateWebHostBuilder() protected override void ConfigureWebHost(IWebHostBuilder builder) { builder - .ConfigureLogging(loggingBuilder => - { - loggingBuilder.ClearProviders(); - loggingBuilder.AddProvider(new BackendFxLoggerProvider()); - loggingBuilder.AddDebug(); - }) + .ConfigureLogging( + loggingBuilder => + { + loggingBuilder.ClearProviders(); + loggingBuilder.AddProvider(new BackendFxLoggerProvider()); + loggingBuilder.AddDebug(); + }) .CaptureStartupErrors(true) .UseSolutionRelativeContentRoot("") .UseSetting("detailedErrors", true.ToString()) @@ -36,17 +36,17 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) protected override TestServer CreateServer(IWebHostBuilder builder) { - TestServer server = base.CreateServer(builder); + var server = base.CreateServer(builder); - ITenantService tenantService = server.Services.GetRequiredService().TenantService; - for (int i = 0; i < 100; i++) + var tenantService = server.Services.GetRequiredService().TenantService; + for (var i = 0; i < 100; i++) { var x = tenantService.CreateTenant($"t{i:000}", $"Tenant {i:000}", false); Assert.True(x.Value > 0); } - - + + return server; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs b/src/environments/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs similarity index 59% rename from tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs index f8b08194..9a877ffd 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs @@ -17,16 +17,19 @@ public async Task CanBeCalledWithCorrectArguments() using (var client = _factory.CreateClient()) { client.DefaultRequestHeaders.Add("Authorization", $"Bearer {JwtService.IssueJwt("testUser")}"); - var result = await client.PostAsync("/api/calculations/addition/4/8?tenantId=1234", new StringContent("")); - var stringResult = await result.Content.ReadAsStringAsync(); + var result = await client.PostAsync( + "/api/calculations/addition/4/8?tenantId=1234", + new StringContent("")); + string stringResult = await result.Content.ReadAsStringAsync(); Assert.True(result.IsSuccessStatusCode); - var calculationResult = JsonConvert.DeserializeObject(stringResult); + var calculationResult + = JsonConvert.DeserializeObject(stringResult); Assert.Equal(12d, calculationResult.Result); } } - + [Fact] public async Task HandlesClientErrors() { @@ -34,45 +37,52 @@ public async Task HandlesClientErrors() { client.DefaultRequestHeaders.Add("Authorization", $"Bearer {JwtService.IssueJwt("testUser")}"); client.DefaultRequestHeaders.Add("Accept", "application/json"); - var result = await client.PostAsync("/api/calculations/division/4/0?tenantId=1234", new StringContent("")); - var stringResult = await result.Content.ReadAsStringAsync(); + var result = await client.PostAsync( + "/api/calculations/division/4/0?tenantId=1234", + new StringContent("")); + string stringResult = await result.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.UnprocessableEntity, result.StatusCode); Assert.Contains("Division by zero", stringResult); } } - - + [Fact] public async Task MaintainsTheCurrentTenantId() { using (var client = _factory.CreateClient()) { client.DefaultRequestHeaders.Add("Authorization", $"Bearer {JwtService.IssueJwt("testUser")}"); - var result = await client.PostAsync("/api/calculations/addition/4/8?tenantId=1234", new StringContent("")); - var stringResult = await result.Content.ReadAsStringAsync(); + var result = await client.PostAsync( + "/api/calculations/addition/4/8?tenantId=1234", + new StringContent("")); + string stringResult = await result.Content.ReadAsStringAsync(); Assert.True(result.IsSuccessStatusCode); - var calculationResult = JsonConvert.DeserializeObject(stringResult); + var calculationResult + = JsonConvert.DeserializeObject(stringResult); Assert.Equal(1234, calculationResult.TenantId); } } - + [Fact] public async Task MaintainsTheCurrentIdentity() { using (var client = _factory.CreateClient()) { client.DefaultRequestHeaders.Add("Authorization", $"Bearer {JwtService.IssueJwt("testUser")}"); - var result = await client.PostAsync("/api/calculations/addition/4/8?tenantId=1234", new StringContent("")); - var stringResult = await result.Content.ReadAsStringAsync(); + var result = await client.PostAsync( + "/api/calculations/addition/4/8?tenantId=1234", + new StringContent("")); + string stringResult = await result.Content.ReadAsStringAsync(); Assert.True(result.IsSuccessStatusCode); - var calculationResult = JsonConvert.DeserializeObject(stringResult); + var calculationResult + = JsonConvert.DeserializeObject(stringResult); Assert.Equal("testUser", calculationResult.Executor); } } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs b/src/environments/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs similarity index 63% rename from tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs rename to src/environments/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs index bbdbb72e..5a7d4ccf 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs +++ b/src/environments/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs @@ -17,23 +17,31 @@ public async Task ProvidesTenant() using (var client = _factory.CreateClient()) { var response = await client.GetAsync("/api/tenants/21"); - var responseContent = await response.Content.ReadAsStringAsync(); - Assert.True(response.IsSuccessStatusCode, $"{(int)response.StatusCode}: {response.StatusCode.ToString()}"); - var tenant = JsonConvert.DeserializeAnonymousType(responseContent, new {Id = 0, Name = "", Description = ""}); + string responseContent = await response.Content.ReadAsStringAsync(); + Assert.True( + response.IsSuccessStatusCode, + $"{(int)response.StatusCode}: {response.StatusCode.ToString()}"); + var tenant = JsonConvert.DeserializeAnonymousType( + responseContent, + new { Id = 0, Name = "", Description = "" }); Assert.NotNull(tenant); Assert.Equal(21, tenant.Id); } } - + [Fact] public async Task ProvidesListOfTenants() { using (var client = _factory.CreateClient()) { var response = await client.GetAsync("/api/tenants"); - var responseContent = await response.Content.ReadAsStringAsync(); - Assert.True(response.IsSuccessStatusCode, $"{(int)response.StatusCode}: {response.StatusCode.ToString()}"); - var tenants = JsonConvert.DeserializeAnonymousType(responseContent, new[] {new {Id = 0, Name = "", Description = ""}}); + string responseContent = await response.Content.ReadAsStringAsync(); + Assert.True( + response.IsSuccessStatusCode, + $"{(int)response.StatusCode}: {response.StatusCode.ToString()}"); + var tenants = JsonConvert.DeserializeAnonymousType( + responseContent, + new[] { new { Id = 0, Name = "", Description = "" } }); Assert.NotEmpty(tenants); Assert.True(tenants.Length >= 100); } @@ -52,15 +60,19 @@ public async Task CanCreateTenant() IsDemo = false, Name = "Hello" }; - + var response = await client.PostAsJsonAsync("/api/tenants", createTenantParams); - var responseContent = await response.Content.ReadAsStringAsync(); - Assert.True(response.IsSuccessStatusCode, $"{(int)response.StatusCode}: {response.StatusCode.ToString()}"); - - var tenant = JsonConvert.DeserializeAnonymousType(responseContent, new {Id = 0, Name = "", Description = ""}); + string responseContent = await response.Content.ReadAsStringAsync(); + Assert.True( + response.IsSuccessStatusCode, + $"{(int)response.StatusCode}: {response.StatusCode.ToString()}"); + + var tenant = JsonConvert.DeserializeAnonymousType( + responseContent, + new { Id = 0, Name = "", Description = "" }); Assert.True(tenant.Id != 0); Assert.Equal(createTenantParams.Name, tenant.Name); } } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj index 5e448048..d4c2c7d5 100644 --- a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj +++ b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 Library true snupkg @@ -36,7 +36,10 @@ - + + + + diff --git a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs index a2bfddb4..6b0c52dd 100644 --- a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs +++ b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs @@ -11,7 +11,8 @@ public interface IBackendFxApplicationHostedService : IHostedService { IBackendFxApplication Application { get; } } - + + public abstract class BackendFxApplicationHostedService : IBackendFxApplicationHostedService { private static readonly ILogger Logger = LogManager.Create(); @@ -42,4 +43,4 @@ public Task StopAsync(CancellationToken cancellationToken) } } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs index da15bc23..4a03091a 100644 --- a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs +++ b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs @@ -1,10 +1,9 @@ -using System.Security.Principal; using Backend.Fx.AspNetCore.MultiTenancy; using Backend.Fx.AspNetCore.Mvc; using Backend.Fx.AspNetCore.Mvc.Activators; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -17,8 +16,8 @@ public static void AddBackendFxApplication(this IServiceCollecti { services.AddSingleton(); services.AddSingleton(provider => provider.GetRequiredService()); - services.AddSingleton(provider => provider.GetRequiredService().Application); services.AddSingleton(); + services.AddSingleton(); } public static void UseBackendFxApplication(this IApplicationBuilder app) @@ -26,22 +25,29 @@ public static void UseBackendFxApplication(th { app.UseMiddleware(); - app.Use(async (context, requestDelegate) => - { - IBackendFxApplication application = app.ApplicationServices.GetRequiredService().Application; - application.WaitForBoot(); + app.Use( + async (context, requestDelegate) => + { + // the ambient tenant id has been set before by a TenantMiddleware + var tenantId = context.GetTenantId(); - // set the instance provider for the controller activator - context.SetCurrentInstanceProvider(application.CompositionRoot.InstanceProvider); + // the invoking identity has been set before by an AuthenticationMiddleware + var actingIdentity = context.User.Identity; - // the ambient tenant id has been set before by a TenantMiddleware - var tenantId = context.GetTenantId(); + var application = app.ApplicationServices.GetRequiredService().Application; - // the invoking identity has been set before by an AuthenticationMiddleware - IIdentity actingIdentity = context.User.Identity; + await application.AsyncInvoker.InvokeAsync( + ip => + { + // set the instance provider for activators being called inside the requestDelegate (everything related to MVC + // for example, like ControllerActivator and ViewComponentActivator etc.) + context.SetCurrentInstanceProvider(ip); - await application.AsyncInvoker.InvokeAsync(_ => requestDelegate.Invoke(), actingIdentity, tenantId); - }); + return requestDelegate.Invoke(); + }, + actingIdentity, + tenantId); + }); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs deleted file mode 100644 index 7c8de079..00000000 --- a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Http; - -namespace Backend.Fx.AspNetCore.Bootstrapping -{ - /// - /// Queues all requests until the application finished booting. - /// - public class WaitForBootMiddleware - { - private static readonly ILogger Logger = LogManager.Create(); - private readonly RequestDelegate _next; - private readonly IBackendFxApplication _application; - - [UsedImplicitly] - public WaitForBootMiddleware(RequestDelegate next, IBackendFxApplication application) - { - _next = next; - _application = application; - } - - /// - /// This method is being called by the previous middleware in the HTTP pipeline - /// - [UsedImplicitly] - public async Task Invoke(HttpContext context) - { - while (!_application.WaitForBoot(3000)) - { - Logger.Info("Queuing Request while application is booting..."); - } - - await _next.Invoke(context); - } - } -} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs b/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs index d01f9edf..ad2e1a91 100644 --- a/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs @@ -7,7 +7,7 @@ public static class ConfigurationEx { public static TOptions Load(this IConfiguration configuration) where TOptions : class, new() { - IConfigurationSection configurationSection = configuration.GetSection(typeof(TOptions).Name); + var configurationSection = configuration.GetSection(typeof(TOptions).Name); var configurationOptions = new NamedConfigureFromConfigurationOptions( typeof(TOptions).Name, configurationSection); @@ -16,4 +16,4 @@ public static class ConfigurationEx return options; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs index c8bfdf9e..b6d7c6a2 100644 --- a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs @@ -33,42 +33,64 @@ public async Task Invoke(HttpContext context) { await _next.Invoke(context); } - catch (TooManyRequestsException tmrex) + catch (TooManyRequestsException tooManyRequestsException) { - if (tmrex.RetryAfter > 0) + if (tooManyRequestsException.RetryAfter > 0) { - context.Response.Headers.Add("Retry-After", tmrex.RetryAfter.ToString(CultureInfo.InvariantCulture)); + context.Response.Headers.Add( + "Retry-After", + tooManyRequestsException.RetryAfter.ToString(CultureInfo.InvariantCulture)); } - await HandleClientError(context, 429, "TooManyRequests", tmrex); + await HandleClientError(context, 429, "TooManyRequests", tooManyRequestsException); } - catch (UnprocessableException uex) + catch (UnprocessableException unprocessableException) { - await HandleClientError(context, 422, "Unprocessable", uex); + await HandleClientError(context, 422, "Unprocessable", unprocessableException); } - catch (NotFoundException nfex) + catch (NotFoundException notFoundException) { - await HandleClientError(context, (int) HttpStatusCode.NotFound, HttpStatusCode.NotFound.ToString(), nfex); + await HandleClientError( + context, + (int)HttpStatusCode.NotFound, + HttpStatusCode.NotFound.ToString(), + notFoundException); } - catch (ConflictedException confex) + catch (ConflictedException conflictedException) { - await HandleClientError(context, (int) HttpStatusCode.Conflict, HttpStatusCode.Conflict.ToString(), confex); + await HandleClientError( + context, + (int)HttpStatusCode.Conflict, + HttpStatusCode.Conflict.ToString(), + conflictedException); } - catch (ForbiddenException uex) + catch (ForbiddenException forbiddenException) { - await HandleClientError(context, (int) HttpStatusCode.Forbidden, HttpStatusCode.Forbidden.ToString(), uex); + await HandleClientError( + context, + (int)HttpStatusCode.Forbidden, + HttpStatusCode.Forbidden.ToString(), + forbiddenException); } - catch (UnauthorizedException uex) + catch (UnauthorizedException unauthorizedException) { - await HandleClientError(context, (int) HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized.ToString(), uex); + await HandleClientError( + context, + (int)HttpStatusCode.Unauthorized, + HttpStatusCode.Unauthorized.ToString(), + unauthorizedException); } - catch (ClientException cex) + catch (ClientException clientException) { - await HandleClientError(context, (int) HttpStatusCode.BadRequest, HttpStatusCode.BadRequest.ToString(), cex); + await HandleClientError( + context, + (int)HttpStatusCode.BadRequest, + HttpStatusCode.BadRequest.ToString(), + clientException); } - catch (Exception ex) + catch (Exception exception) { - await HandleServerError(context, ex); + await HandleServerError(context, exception); } } else @@ -79,8 +101,12 @@ public async Task Invoke(HttpContext context) protected abstract Task ShouldHandle(HttpContext context); - protected abstract Task HandleClientError(HttpContext context, int httpStatusCode, string message, ClientException exception); + protected abstract Task HandleClientError( + HttpContext context, + int httpStatusCode, + string message, + ClientException exception); protected abstract Task HandleServerError(HttpContext context, Exception exception); } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorLoggingMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorLoggingMiddleware.cs index b384b1e1..b548d2d3 100644 --- a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorLoggingMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorLoggingMiddleware.cs @@ -1,15 +1,15 @@ using System; using System.Threading.Tasks; -using JetBrains.Annotations; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.AspNetCore.Http; namespace Backend.Fx.AspNetCore.ErrorHandling { public class ErrorLoggingMiddleware { - private readonly RequestDelegate _next; private readonly IExceptionLogger _exceptionLogger; + private readonly RequestDelegate _next; [UsedImplicitly] public ErrorLoggingMiddleware(RequestDelegate next, IExceptionLogger exceptionLogger) @@ -37,4 +37,4 @@ public async Task Invoke(HttpContext context) } } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs index 69334a0e..33838a58 100644 --- a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs @@ -14,23 +14,23 @@ namespace Backend.Fx.AspNetCore.ErrorHandling { public class JsonErrorHandlingMiddleware : ErrorHandlingMiddleware { - private readonly bool _showInternalServerErrorDetails; private static readonly ILogger Logger = LogManager.Create(); + private readonly bool _showInternalServerErrorDetails; - protected JsonSerializerSettings JsonSerializerSettings { get; } = new JsonSerializerSettings - { - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy {ProcessDictionaryKeys = true} - }, - }; - - public JsonErrorHandlingMiddleware(RequestDelegate next, bool showInternalServerErrorDetails) + protected JsonErrorHandlingMiddleware(RequestDelegate next, bool showInternalServerErrorDetails) : base(next) { _showInternalServerErrorDetails = showInternalServerErrorDetails; } + protected JsonSerializerSettings JsonSerializerSettings { get; } = new() + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true } + } + }; + protected override Task ShouldHandle(HttpContext context) { // this middleware only handles requests that accept json as response @@ -38,7 +38,11 @@ protected override Task ShouldHandle(HttpContext context) return Task.FromResult(accept?.Any(mth => mth.Type == "application" && mth.SubType == "json") == true); } - protected override async Task HandleClientError(HttpContext context, int httpStatusCode, string message, ClientException exception) + protected override async Task HandleClientError( + HttpContext context, + int httpStatusCode, + string message, + ClientException exception) { if (context.Response.HasStarted) { @@ -48,9 +52,9 @@ protected override async Task HandleClientError(HttpContext context, int httpSta // convention: only the errors array will be transmitted to the client, allowing technical (possibly // revealing) information in the exception message. - Errors errors = exception.HasErrors() - ? exception.Errors - : new Errors().Add($"HTTP{httpStatusCode}: {message}"); + var errors = exception.HasErrors() + ? exception.Errors + : new Errors().Add($"HTTP{httpStatusCode}: {message}"); context.Response.StatusCode = httpStatusCode; string serializedErrors = SerializeErrors(errors); @@ -66,30 +70,35 @@ protected override async Task HandleServerError(HttpContext context, Exception e return; } - context.Response.StatusCode = (int) HttpStatusCode.InternalServerError; - var responseContent = _showInternalServerErrorDetails - ? JsonConvert.SerializeObject(new {message = exception.Message, stackTrace = exception.StackTrace}, JsonSerializerSettings) - : JsonConvert.SerializeObject(new {message = "An internal error occured"}, JsonSerializerSettings); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + string responseContent = _showInternalServerErrorDetails + ? JsonConvert.SerializeObject( + new { message = exception.Message, stackTrace = exception.StackTrace }, + JsonSerializerSettings) + : JsonConvert.SerializeObject(new { message = "An internal error occured" }, JsonSerializerSettings); context.Response.ContentType = "application/json; charset=utf-8"; await context.Response.WriteAsync(responseContent); } protected virtual string SerializeErrors(Errors errors) { - var errorsDictionaryForJson = errors.ToDictionary(kvp => kvp.Key == "" ? "_error" : kvp.Key, kvp => kvp.Value); + Dictionary errorsDictionaryForJson = errors.ToDictionary( + kvp => kvp.Key == "" ? "_error" : kvp.Key, + kvp => kvp.Value); return JsonConvert.SerializeObject(errorsDictionaryForJson, JsonSerializerSettings); } } - + + public class ErrorShape { - public Dictionary Errors { get; set; } + public Dictionary Errors { get; set; } public string[] GenericError { get { - Errors.TryGetValue("_error", out var genericError); + Errors.TryGetValue("_error", out string[] genericError); return genericError; } } @@ -99,4 +108,4 @@ public bool HasOnlyGenericError() return Errors.Count == 1 && Errors.Keys.Single() == "_error"; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs b/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs index d1c337dd..a513f817 100644 --- a/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs @@ -14,7 +14,7 @@ public static bool IsRestfulSafe(this HttpRequest request) { return request.IsGet() || request.IsOptions() || request.IsHead(); } - + /// /// Is the request method considered as idempotent in sense of a RESTful API? /// See https://restcookbook.com/HTTP%20Methods/idempotency/ @@ -30,45 +30,45 @@ public static bool IsGet(this HttpRequest request) { return HttpMethods.IsGet(request.Method); } - + public static bool IsConnect(this HttpRequest request) { return HttpMethods.IsConnect(request.Method); } - + public static bool IsDelete(this HttpRequest request) { return HttpMethods.IsDelete(request.Method); } - + public static bool IsHead(this HttpRequest request) { return HttpMethods.IsHead(request.Method); } - + public static bool IsOptions(this HttpRequest request) { return HttpMethods.IsOptions(request.Method); } - + public static bool IsPatch(this HttpRequest request) { return HttpMethods.IsPatch(request.Method); } - + public static bool IsPost(this HttpRequest request) { return HttpMethods.IsPost(request.Method); } - + public static bool IsPut(this HttpRequest request) { return HttpMethods.IsPut(request.Method); } - + public static bool IsTrace(this HttpRequest request) { return HttpMethods.IsTrace(request.Method); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs b/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs index 01355ab0..78a4a17a 100644 --- a/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs @@ -6,23 +6,27 @@ namespace Backend.Fx.AspNetCore { public static class HttpResponseEx { - public static async Task WriteJsonAsync(this HttpResponse response, object o, JsonSerializerOptions options = null, string contentType = null) + public static async Task WriteJsonAsync( + this HttpResponse response, + object o, + JsonSerializerOptions options = null, + string contentType = null) { options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, AllowTrailingCommas = true, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase }; - + await response.WriteJsonAsync(JsonSerializer.Serialize(o, options), contentType); } - + public static async Task WriteJsonAsync(this HttpResponse response, string json, string contentType = null) { - response.ContentType = (contentType ?? "application/json; charset=UTF-8"); + response.ContentType = contentType ?? "application/json; charset=UTF-8"; await response.WriteAsync(json); await response.Body.FlushAsync(); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/CreateTenantParams.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/CreateTenantParams.cs index 52957e3c..48c44bd1 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/CreateTenantParams.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/CreateTenantParams.cs @@ -7,12 +7,12 @@ public class CreateTenantParams [JsonProperty(PropertyName = "isDemo")] public bool IsDemo { get; set; } - [JsonProperty(PropertyName = "name")] + [JsonProperty(PropertyName = "name")] public string Name { get; set; } [JsonProperty(PropertyName = "description")] public string Description { get; set; } - + [JsonProperty(PropertyName = "administratorEmail")] public string AdministratorEmail { get; set; } @@ -22,4 +22,4 @@ public class CreateTenantParams [JsonProperty(PropertyName = "configuration")] public string Configuration { get; set; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs index 09876ccb..48988b28 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs @@ -12,20 +12,21 @@ public static void SetCurrentTenantId(this HttpContext httpContext, TenantId ten { if (httpContext.Items.TryGetValue(TenantId, out object untyped)) { - throw new InvalidOperationException($"TenantId has been set already in this HttpContext. Value: {(untyped ?? "null")}"); + throw new InvalidOperationException( + $"TenantId has been set already in this HttpContext. Value: {untyped ?? "null"}"); } httpContext.Items[TenantId] = tenantId; } - + public static TenantId GetTenantId(this HttpContext httpContext) { if (httpContext.Items.TryGetValue(TenantId, out object untyped)) { - return (TenantId) untyped; + return (TenantId)untyped; } throw new InvalidOperationException("No TenantId present in this HttpContext"); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/MultiTenantMiddlewareBase.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/MultiTenantMiddlewareBase.cs index 60c01c15..dca976c4 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/MultiTenantMiddlewareBase.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/MultiTenantMiddlewareBase.cs @@ -12,20 +12,22 @@ protected MultiTenantMiddlewareBase(RequestDelegate next) { _next = next; } - + public async Task Invoke(HttpContext context) { context.SetCurrentTenantId(FindMatchingTenantId(context)); await _next.Invoke(context); } - + /// - /// Detects the for this request from the current HttpContext. Possible implementations might rely on - /// a dedicated header value, the (sub-) domain name, a query string parameter etc. This method is called for each request. If + /// Detects the for this request from the current HttpContext. Possible implementations might rely + /// on + /// a dedicated header value, the (sub-) domain name, a query string parameter etc. This method is called for each request. + /// If /// the database is required for determination, some kind of caching is advised. /// /// /// The TenantId for this request protected abstract TenantId FindMatchingTenantId(HttpContext context); } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs index 92a18301..d6c9aff1 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs @@ -22,4 +22,4 @@ public virtual async Task Invoke(HttpContext context) await _next.Invoke(context); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs index d01051d0..57639f37 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs @@ -15,15 +15,17 @@ public abstract class TenantAdminMiddlewareBase { private static readonly ILogger Logger = LogManager.Create(); private readonly RequestDelegate _next; - protected virtual string TenantsApiBaseUrl { get; } = "/api/tenants"; - protected ITenantService TenantService { get; } protected TenantAdminMiddlewareBase(RequestDelegate next, ITenantService tenantService) { _next = next; TenantService = tenantService; } - + + protected virtual string TenantsApiBaseUrl { get; } = "/api/tenants"; + + protected ITenantService TenantService { get; } + public async Task Invoke(HttpContext context) { if (context.Request.Path.StartsWithSegments(TenantsApiBaseUrl)) @@ -31,33 +33,37 @@ public async Task Invoke(HttpContext context) if (!IsTenantsAdmin(context)) { Logger.Warn("Unauthorized attempt to access tenant endpoints"); - context.Response.StatusCode = (int) HttpStatusCode.Forbidden; + context.Response.StatusCode = (int)HttpStatusCode.Forbidden; return; } if (context.Request.Method.ToLower() == "post") { Logger.Info("Creating Tenant"); - + try { using (var inputStream = new StreamReader(context.Request.Body)) { string inputStreamContent = await inputStream.ReadToEndAsync(); - var createTenantParams = JsonConvert.DeserializeObject(inputStreamContent); - if (createTenantParams == null) throw new ClientException(); + var createTenantParams + = JsonConvert.DeserializeObject(inputStreamContent); + if (createTenantParams == null) + { + throw new ClientException(); + } - Tenant tenant = await CreateTenant(createTenantParams); + var tenant = await CreateTenant(createTenantParams); Logger.Info($"Created Tenant[{tenant.Id}] ({tenant.Name})"); - + await context.Response.WriteJsonAsync(tenant); } } catch (Exception ex) { Logger.Error(ex, "Tenant Creation failed"); - context.Response.StatusCode = (int) HttpStatusCode.BadRequest; + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; await context.Response.WriteAsync(ex.Message); return; } @@ -67,7 +73,7 @@ public async Task Invoke(HttpContext context) if (HttpMethods.IsGet(context.Request.Method)) { - var tenantIdStr = context.Request.Path.Value.Split('/').Last(); + string tenantIdStr = context.Request.Path.Value.Split('/').Last(); if (int.TryParse(tenantIdStr, out int tenantId)) { Logger.Info($"Getting Tenant[{tenantId}]"); @@ -77,9 +83,9 @@ public async Task Invoke(HttpContext context) } else { - Logger.Info($"Getting Tenants"); - - var tenants = TenantService.GetTenants(); + Logger.Info("Getting Tenants"); + + Tenant[] tenants = TenantService.GetTenants(); await context.Response.WriteJsonAsync(tenants); } @@ -95,13 +101,13 @@ private async Task CreateTenant(CreateTenantParams createTenantParams) var tenantId = TenantService.CreateTenant( createTenantParams.Name, createTenantParams.Description, - createTenantParams.IsDemo, + createTenantParams.IsDemo, GetTenantConfiguration(createTenantParams)); await AfterTenantCreation(createTenantParams, tenantId); var tenant = TenantService.GetTenant(tenantId); return tenant; } - + protected abstract string GetTenantConfiguration(CreateTenantParams createTenantParams); protected virtual Task AfterTenantCreation(CreateTenantParams createTenantParams, TenantId tenantId) @@ -111,4 +117,4 @@ protected virtual Task AfterTenantCreation(CreateTenantParams createTenantParams protected abstract bool IsTenantsAdmin(HttpContext context); } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs index c8cfec28..d5ecd580 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs @@ -7,9 +7,9 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators { /// - /// This controller activator relies on an set before in the + /// This controller activator relies on an set before in the /// http context items dictionary. If non is to be found, the controller is activated - /// using the default (without providing any ctor arguments). + /// using the default (without providing any ctor arguments). /// public class BackendFxApplicationControllerActivator : IControllerActivator { @@ -18,12 +18,22 @@ public class BackendFxApplicationControllerActivator : IControllerActivator public virtual object Create(ControllerContext c) { var requestedControllerType = c.ActionDescriptor.ControllerTypeInfo.AsType(); - - return c.HttpContext.TryGetInstanceProvider(out var ip) + + return c.HttpContext.TryGetInstanceProvider(out var ip) ? CreateInstanceUsingInstanceProvider(ip, requestedControllerType) : CreateInstanceUsingSystemActivator(requestedControllerType); } + public virtual void Release(ControllerContext c, object controller) + { + Logger.Trace($"Releasing {controller.GetType().Name}"); + if (controller is IDisposable disposable) + { + Logger.Debug($"Disposing {controller.GetType().Name}"); + disposable.Dispose(); + } + } + private static object CreateInstanceUsingInstanceProvider(object ip, Type requestedControllerType) { Logger.Debug($"Providing {requestedControllerType.Name} using {ip.GetType().Name}"); @@ -35,15 +45,5 @@ private static object CreateInstanceUsingSystemActivator(Type requestedControlle Logger.Debug($"Providing {requestedControllerType.Name} using {nameof(Activator)}"); return Activator.CreateInstance(requestedControllerType); } - - public virtual void Release(ControllerContext c, object controller) - { - Logger.Trace($"Releasing {controller.GetType().Name}"); - if (controller is IDisposable disposable) - { - Logger.Debug($"Disposing {controller.GetType().Name}"); - disposable.Dispose(); - } - } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs index 4949ef92..e02986d7 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs @@ -7,16 +7,14 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators { public class BackendFxApplicationHubActivator : IHubActivator where T : Hub { - private readonly IBackendFxApplication _backendFxApplication; private static readonly ILogger Logger = LogManager.Create>(); - + private readonly IBackendFxApplication _backendFxApplication; public BackendFxApplicationHubActivator(IBackendFxApplication backendFxApplication) { _backendFxApplication = backendFxApplication; } - public T Create() { var ip = _backendFxApplication.CompositionRoot.InstanceProvider; @@ -34,4 +32,4 @@ public void Release(T hub) } } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs index 4f1f3d92..2bcd62b5 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs @@ -8,16 +8,19 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators public class BackendFxApplicationViewComponentActivator : IViewComponentActivator { private static readonly ILogger Logger = LogManager.Create(); - + public object Create(ViewComponentContext context) { var requestedViewComponentType = context.ViewComponentDescriptor.TypeInfo.AsType(); - - return context.ViewContext.HttpContext.TryGetInstanceProvider(out var ip) + + return context.ViewContext.HttpContext.TryGetInstanceProvider(out var ip) ? CreateInstanceUsingInstanceProvider(ip, requestedViewComponentType) : CreateInstanceUsingSystemActivator(requestedViewComponentType); } + public void Release(ViewComponentContext context, object viewComponent) + { } + private static object CreateInstanceUsingInstanceProvider(object ip, Type requestedViewComponentType) { Logger.Debug($"Providing {requestedViewComponentType.Name} using {ip.GetType().Name}"); @@ -29,9 +32,5 @@ private static object CreateInstanceUsingSystemActivator(Type requestedViewCompo Logger.Debug($"Providing {requestedViewComponentType.Name} using {nameof(Activator)}"); return Activator.CreateInstance(requestedViewComponentType); } - - public void Release(ViewComponentContext context, object viewComponent) - { - } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs index 2691e7a2..f4d4f0cb 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs @@ -4,7 +4,8 @@ namespace Backend.Fx.AspNetCore.Mvc.Execution { /// - /// Makes sure that possible dirty objects are flushed to the persistence layer when the MVC action was executed. This will reveal + /// Makes sure that possible dirty objects are flushed to the persistence layer when the MVC action was executed. This will + /// reveal /// persistence related problems early and makes them easier to diagnose. /// public class FlushFilter : IActionFilter @@ -17,4 +18,4 @@ public void OnActionExecuted(ActionExecutedContext context) context.HttpContext.GetInstanceProvider().GetInstance().Flush(); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs index e8185a3e..8184ea9e 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs @@ -10,7 +10,7 @@ public static class HttpContextEx public static void SetCurrentInstanceProvider(this HttpContext httpContext, IInstanceProvider tenantId) { - if (httpContext.Items.TryGetValue(InstanceProvider, out object untyped)) + if (httpContext.Items.TryGetValue(InstanceProvider, out object _)) { throw new InvalidOperationException("IInstanceProvider has been set already in this HttpContext"); } @@ -22,7 +22,7 @@ public static IInstanceProvider GetInstanceProvider(this HttpContext httpContext { if (httpContext.Items.TryGetValue(InstanceProvider, out object untyped)) { - return (IInstanceProvider) untyped; + return (IInstanceProvider)untyped; } throw new InvalidOperationException("No IInstanceProvider present in this HttpContext"); @@ -32,7 +32,7 @@ public static bool TryGetInstanceProvider(this HttpContext httpContext, out IIns { if (httpContext.Items.TryGetValue(InstanceProvider, out object untyped)) { - instanceProvider = (IInstanceProvider) untyped; + instanceProvider = (IInstanceProvider)untyped; return true; } @@ -40,4 +40,4 @@ public static bool TryGetInstanceProvider(this HttpContext httpContext, out IIns return false; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs index 4ed56721..aeb8535f 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs @@ -15,7 +15,7 @@ public class ExceptionThrottlingAttribute : ThrottlingBaseAttribute public override void OnActionExecuted(ActionExecutedContext actionContext) { var cache = actionContext.HttpContext.RequestServices.GetRequiredService(); - var key = string.Concat(Name, "-", actionContext.HttpContext.Connection.RemoteIpAddress); + string key = string.Concat(Name, "-", actionContext.HttpContext.Connection.RemoteIpAddress); if (actionContext.Exception == null) { @@ -25,7 +25,7 @@ public override void OnActionExecuted(ActionExecutedContext actionContext) if (cache.TryGetValue(key, out int repetition)) { - var retryAfter = Math.Max(1, CalculateRepeatedTimeoutFactor(repetition)) * Seconds; + int retryAfter = Math.Max(1, CalculateRepeatedTimeoutFactor(repetition)) * Seconds; cache.Set(key, ++repetition, TimeSpan.FromSeconds(retryAfter)); throw new TooManyRequestsException(retryAfter).AddError(string.Format(Message, retryAfter)); } @@ -38,4 +38,4 @@ protected override int CalculateRepeatedTimeoutFactor(int repetition) return repetition * repetition; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs index 031a53f8..d79bd7a6 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs @@ -15,12 +15,12 @@ public class ThrottlingAttribute : ThrottlingBaseAttribute public override void OnActionExecuting(ActionExecutingContext actionContext) { var cache = actionContext.HttpContext.RequestServices.GetRequiredService(); - var key = string.Concat(Name, "-", actionContext.HttpContext.Connection.RemoteIpAddress); + string key = string.Concat(Name, "-", actionContext.HttpContext.Connection.RemoteIpAddress); if (cache.TryGetValue(key, out int repetition)) { repetition++; - var retryAfter = Math.Max(1, CalculateRepeatedTimeoutFactor(repetition)) * Seconds; + int retryAfter = Math.Max(1, CalculateRepeatedTimeoutFactor(repetition)) * Seconds; cache.Set(key, repetition, TimeSpan.FromSeconds(retryAfter)); throw new TooManyRequestsException(retryAfter).AddError(string.Format(Message, retryAfter)); } @@ -28,4 +28,4 @@ public override void OnActionExecuting(ActionExecutingContext actionContext) cache.Set(key, 1, TimeSpan.FromSeconds(Seconds)); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingBaseAttribute.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingBaseAttribute.cs index b10e2ee2..c690bac3 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingBaseAttribute.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingBaseAttribute.cs @@ -32,4 +32,4 @@ protected virtual int CalculateRepeatedTimeoutFactor(int repetition) return 1; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelStateEx.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelStateEx.cs index b06d1c4f..da4c4d2f 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelStateEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelStateEx.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Backend.Fx.Exceptions; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -8,9 +9,9 @@ public static class ModelStateEx { public static string ToDebugString(this ModelStateDictionary modelState) { - var modelErrorMessages = modelState - .Where(kvp => kvp.Value.ValidationState == ModelValidationState.Invalid) - .Select(kvp => $"{kvp.Key}: {string.Join(", ", kvp.Value.Errors.Select(err => err.ErrorMessage))}"); + IEnumerable modelErrorMessages = modelState + .Where(kvp => kvp.Value.ValidationState == ModelValidationState.Invalid) + .Select(kvp => $"{kvp.Key}: {string.Join(", ", kvp.Value.Errors.Select(err => err.ErrorMessage))}"); return string.Join(System.Environment.NewLine, modelErrorMessages); } @@ -18,7 +19,7 @@ public static string ToDebugString(this ModelStateDictionary modelState) public static Errors ToErrorsDictionary(this ModelStateDictionary modelState) { var errors = new Errors(); - foreach (var keyValuePair in modelState) + foreach (KeyValuePair keyValuePair in modelState) { errors.Add(keyValuePair.Key, keyValuePair.Value.Errors.Select(err => err.ErrorMessage)); } @@ -26,16 +27,15 @@ public static Errors ToErrorsDictionary(this ModelStateDictionary modelState) return errors; } - public static void Add(this ModelStateDictionary modelState, Errors errors) { - foreach (var keyValuePair in errors) + foreach (KeyValuePair keyValuePair in errors) { - foreach (var errorMessage in keyValuePair.Value) + foreach (string errorMessage in keyValuePair.Value) { modelState.AddModelError(keyValuePair.Key, errorMessage); } } } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs index 196512d6..20c0fd17 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs @@ -16,11 +16,12 @@ public abstract class ModelValidationFilter : IActionFilter protected void LogErrors(FilterContext context, string controllerName, Errors errors) { - ILogger logger = TryGetControllerType(controllerName, out Type controllerType) + var logger = TryGetControllerType(controllerName, out var controllerType) ? LogManager.Create(controllerType) : LogManager.Create(); - logger.Warn($"Model validation failed during {context.HttpContext.Request.Method} {context.HttpContext.Request.PathBase}: " + - string.Join(System.Environment.NewLine, errors.Select(err => err.ToString()))); + logger.Warn( + $"Model validation failed during {context.HttpContext.Request.Method} {context.HttpContext.Request.PathBase}: " + + string.Join(System.Environment.NewLine, errors.Select(err => err.ToString()))); } protected bool AcceptsJson(FilterContext context) @@ -49,4 +50,4 @@ private static bool TryGetControllerType(string controllerName, out Type type) return type != null; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs index 851e0b22..36c48cc2 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs @@ -19,7 +19,7 @@ public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid && AcceptsHtml(context)) { - Errors errors = context.ModelState.ToErrorsDictionary(); + var errors = context.ModelState.ToErrorsDictionary(); LogErrors(context, context.Controller.ToString(), errors); // return the same view, using the posted model again @@ -28,7 +28,7 @@ public override void OnActionExecuting(ActionExecutingContext context) context.Result = new ViewResult { ViewName = context.RouteData.Values["action"].ToString(), - ViewData = viewData, + ViewData = viewData }; } } @@ -46,14 +46,13 @@ public override void OnActionExecuted(ActionExecutedContext context) context.Result = new ViewResult { ViewName = context.RouteData.Values["action"].ToString(), - ViewData = viewData, + ViewData = viewData }; context.ExceptionHandled = true; } } protected virtual void BeforeRedirect(ViewDataDictionary viewData) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs index 43909a58..992f9a3f 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs @@ -5,8 +5,9 @@ namespace Backend.Fx.AspNetCore.Mvc.Validation { /// - /// Returns HTTP 400 "Bad Request" when model validation failed. In addition, the bad model state is converted into an instance of - /// gets serialized to the body as JSON. + /// Returns HTTP 400 "Bad Request" when model validation failed. In addition, the bad model state is converted into an + /// instance of + /// gets serialized to the body as JSON. /// public class ReturnModelStateAsJsonModelValidationFilter : ModelValidationFilter { @@ -14,7 +15,7 @@ public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid && AcceptsJson(context)) { - Errors errors = context.ModelState.ToErrorsDictionary(); + var errors = context.ModelState.ToErrorsDictionary(); LogErrors(context, context.Controller.ToString(), errors); context.Result = CreateResult(errors); } @@ -26,7 +27,6 @@ protected virtual IActionResult CreateResult(Errors errors) } public override void OnActionExecuted(ActionExecutedContext context) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs b/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs index cb04455d..f2f8ec01 100644 --- a/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs +++ b/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs @@ -3,7 +3,9 @@ public class ContentSecurityPolicyOptions { public string ContentSecurityPolicy { get; set; } + public bool ReportOnly { get; set; } + public string ReportUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs index 595bc347..d957982a 100644 --- a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs @@ -12,7 +12,6 @@ public class SecurityHeadersMiddleware private readonly RequestDelegate _next; private readonly IOptions _securityOptionsAccessor; - [UsedImplicitly] public SecurityHeadersMiddleware(RequestDelegate next, IOptions securityOptionsAccessor) { @@ -23,10 +22,11 @@ public SecurityHeadersMiddleware(RequestDelegate next, IOptions 0) { - string cspHeaderKey = csp.ReportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy"; + string cspHeaderKey + = csp.ReportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy"; string completeCsp = csp.ContentSecurityPolicy; @@ -40,7 +40,9 @@ public async Task Invoke(HttpContext context) if (_securityOptionsAccessor.Value.HstsExpiration > 0) { - context.Response.Headers.Add("Strict-Transport-Security", new StringValues($"max-age={_securityOptionsAccessor.Value.HstsExpiration}")); + context.Response.Headers.Add( + "Strict-Transport-Security", + new StringValues($"max-age={_securityOptionsAccessor.Value.HstsExpiration}")); } await _next.Invoke(context); @@ -56,4 +58,4 @@ protected virtual bool ShouldAppendReportUri(HttpContext context) return true; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs b/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs index e4a8a0b6..1388bb18 100644 --- a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs +++ b/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs @@ -3,6 +3,7 @@ public class SecurityHeadersOptions { public int HstsExpiration { get; set; } + public ContentSecurityPolicyOptions ContentSecurityPolicy { get; set; } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.AspNetCore/Versioning/VersionHeaderMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/Versioning/VersionHeaderMiddleware.cs index ea4908e8..d6988d9e 100644 --- a/src/environments/Backend.Fx.AspNetCore/Versioning/VersionHeaderMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/Versioning/VersionHeaderMiddleware.cs @@ -8,10 +8,10 @@ namespace Backend.Fx.AspNetCore.Versioning { [UsedImplicitly] - public class VersionHeaderMiddleware + public class VersionHeaderMiddleware { - private readonly RequestDelegate _next; private readonly string _assemblyName; + private readonly RequestDelegate _next; private readonly string _version; public VersionHeaderMiddleware(RequestDelegate next) @@ -20,13 +20,15 @@ public VersionHeaderMiddleware(RequestDelegate next) var entryAssembly = Assembly.GetEntryAssembly(); if (entryAssembly == null) { - throw new InvalidOperationException("Unable to determine the entry assembly. The Version Header Middleware cannot be used in this environment"); + throw new InvalidOperationException( + "Unable to determine the entry assembly. The Version Header Middleware cannot be used in this environment"); } - AssemblyName entryAssemblyName = entryAssembly.GetName(); + var entryAssemblyName = entryAssembly.GetName(); if (entryAssemblyName.Version == null) { - throw new InvalidOperationException("Unable to determine the version of the entry assembly. The Version Header Middleware cannot be used in this environment"); + throw new InvalidOperationException( + "Unable to determine the version of the entry assembly. The Version Header Middleware cannot be used in this environment"); } _assemblyName = entryAssemblyName.Name; @@ -39,4 +41,4 @@ public async Task InvokeAsync(HttpContext context) await _next.Invoke(context); } } -} \ No newline at end of file +} diff --git a/src/environments/Backend.Fx.NetCore/Backend.Fx.NetCore.csproj b/src/environments/Backend.Fx.NetCore/Backend.Fx.NetCore.csproj deleted file mode 100644 index d472b275..00000000 --- a/src/environments/Backend.Fx.NetCore/Backend.Fx.NetCore.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - netstandard1.3 - true - snupkg - false - false - false - false - - - - Marc Wittke - anic GmbH - All rights reserved. Distributed under the terms of the MIT License. - Integration of Backend.Fx abstraction and implementation into .NET Core applications - False - MIT - https://github.com/marcwittke/Backend.Fx - Backend.Fx - Git - https://github.com/marcwittke/Backend.Fx.git - - - - - - - - - - - - diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs deleted file mode 100644 index c8db9817..00000000 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.IdGeneration; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace Backend.Fx.EfCorePersistence.Bootstrapping -{ - public class EfCorePersistenceModule : IModule - where TDbContext : DbContext - { - private readonly ILoggerFactory _loggerFactory; - private readonly Action, IDbConnection> _configure; - private readonly IDbConnectionFactory _dbConnectionFactory; - private readonly IEntityIdGenerator _entityIdGenerator; - private readonly Assembly[] _assemblies; - - public EfCorePersistenceModule(IDbConnectionFactory dbConnectionFactory, IEntityIdGenerator entityIdGenerator, - ILoggerFactory loggerFactory, Action, IDbConnection> configure, params Assembly[] assemblies) - { - _dbConnectionFactory = dbConnectionFactory; - _entityIdGenerator = entityIdGenerator; - _loggerFactory = loggerFactory; - _configure = configure; - _assemblies = assemblies; - } - - public void Register(ICompositionRoot compositionRoot) - { - // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly - compositionRoot.InfrastructureModule.RegisterScoped(() => _dbConnectionFactory.Create()); - - // singleton id generator - compositionRoot.InfrastructureModule.RegisterInstance(_entityIdGenerator); - - // EF core requires us to flush frequently, because of a missing identity map - compositionRoot.InfrastructureModule.RegisterScoped(); - - // EF Repositories - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IRepository<>), typeof(EfRepository<>)); - - // IQueryable is supported, but should be use with caution, since it bypasses authorization - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IQueryable<>), typeof(EntityQueryable<>)); - - // DbContext is injected into repositories - compositionRoot.InfrastructureModule.RegisterScoped(() => CreateDbContextOptions(compositionRoot.InstanceProvider.GetInstance())); - compositionRoot.InfrastructureModule.RegisterScoped(); - - // wrapping the operation: connection.open - transaction.begin - operation - (flush) - transaction.commit - connection.close - compositionRoot.InfrastructureModule.RegisterDecorator(); - compositionRoot.InfrastructureModule.RegisterDecorator(); - compositionRoot.InfrastructureModule.RegisterDecorator(); - - // ensure everything dirty is flushed to the db before handling domain events - compositionRoot.InfrastructureModule.RegisterDecorator(); - - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IAggregateMapping<>), _assemblies); - } - - protected virtual DbContextOptions CreateDbContextOptions(IDbConnection connection) - { - var dbContextOptionsBuilder = new DbContextOptionsBuilder(); - _configure.Invoke(dbContextOptionsBuilder, connection); - return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs deleted file mode 100644 index c05f8a97..00000000 --- a/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Extensions; -using Backend.Fx.Logging; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; - -namespace Backend.Fx.EfCorePersistence -{ - public static class DbContextExtensions - { - private static readonly ILogger Logger = LogManager.Create(typeof(DbContextExtensions)); - - public static void DisableChangeTracking(this DbContext dbContext) - { - Logger.Debug($"Disabling change tracking on {dbContext.GetType().Name} instance"); - dbContext.ChangeTracker.AutoDetectChangesEnabled = false; - dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - } - - public static void RegisterRowVersionProperty(this ModelBuilder modelBuilder) - { - modelBuilder.Model - .GetEntityTypes() - .Where(mt => typeof(Entity).GetTypeInfo().IsAssignableFrom(mt.ClrType.GetTypeInfo())) - .ForAll(mt => modelBuilder.Entity(mt.ClrType).Property("RowVersion").IsRowVersion()); - } - - public static void RegisterEntityIdAsNeverGenerated(this ModelBuilder modelBuilder) - { - modelBuilder.Model - .GetEntityTypes() - .Where(mt => typeof(Entity).GetTypeInfo().IsAssignableFrom(mt.ClrType.GetTypeInfo())) - .ForAll(mt => modelBuilder.Entity(mt.ClrType).Property(nameof(Entity.Id)).ValueGeneratedNever()); - } - - public static void ApplyAggregateMappings(this DbContext dbContext, ModelBuilder modelBuilder) - { - //CAVE: IAggregateMapping implementations must reside in the same assembly as the Applications DbContext-type - var aggregateDefinitionTypeInfos = dbContext - .GetType() - .GetTypeInfo() - .Assembly - .ExportedTypes - .Select(t => t.GetTypeInfo()) - .Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && typeof(IAggregateMapping).GetTypeInfo().IsAssignableFrom(t)); - foreach (TypeInfo typeInfo in aggregateDefinitionTypeInfos) - { - var aggregateMapping = (IAggregateMapping) Activator.CreateInstance(typeInfo.AsType()); - aggregateMapping.ApplyEfMapping(modelBuilder); - } - } - - - - public static void TraceChangeTrackerState(this DbContext dbContext) - { - if (Logger.IsTraceEnabled()) - try - { - var added = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Added).ToArray(); - var modified = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Modified).ToArray(); - var deleted = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Deleted).ToArray(); - var unchanged = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Unchanged).ToArray(); - - var stateDumpBuilder = new StringBuilder(); - stateDumpBuilder.AppendFormat("{0} entities added{1}{2}", added.Length, deleted.Length == 0 ? "." : ":", System.Environment.NewLine); - foreach (EntityEntry entry in added) - stateDumpBuilder.AppendFormat("added: {0}[{1}]{2}", entry.Entity.GetType().Name, GetPrimaryKeyValue(entry), System.Environment.NewLine); - stateDumpBuilder.AppendFormat("{0} entities modified{1}{2}", modified.Length, deleted.Length == 0 ? "." : ":", System.Environment.NewLine); - foreach (EntityEntry entry in modified) - stateDumpBuilder.AppendFormat("modified: {0}[{1}]{2}", entry.Entity.GetType().Name, GetPrimaryKeyValue(entry), System.Environment.NewLine); - stateDumpBuilder.AppendFormat("{0} entities deleted{1}{2}", deleted.Length, deleted.Length == 0 ? "." : ":", System.Environment.NewLine); - foreach (EntityEntry entry in deleted) - stateDumpBuilder.AppendFormat("deleted: {0}[{1}]{2}", entry.Entity.GetType().Name, GetPrimaryKeyValue(entry), System.Environment.NewLine); - stateDumpBuilder.AppendFormat("{0} entities unchanged{1}{2}", unchanged.Length, deleted.Length == 0 ? "." : ":", System.Environment.NewLine); - foreach (EntityEntry entry in unchanged) - stateDumpBuilder.AppendFormat("unchanged: {0}[{1}]{2}", entry.Entity.GetType().Name, GetPrimaryKeyValue(entry), System.Environment.NewLine); - Logger.Trace(stateDumpBuilder.ToString()); - } - catch (Exception ex) - { - Logger.Warn(ex, "Change tracker state could not be dumped"); - } - } - - private static string GetPrimaryKeyValue(EntityEntry entry) - { - return (entry.Entity as Entity)?.Id.ToString(CultureInfo.InvariantCulture) ?? "?"; - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs deleted file mode 100644 index 58dca37d..00000000 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Security.Principal; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Exceptions; -using Backend.Fx.Extensions; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Backend.Fx.EfCorePersistence -{ - public class EfFlush : ICanFlush - { - private static readonly ILogger Logger = LogManager.Create(); - public DbContext DbContext { get; } - public ICurrentTHolder IdentityHolder { get; } - public IClock Clock { get; } - - public EfFlush(DbContext dbContext, ICurrentTHolder identityHolder, IClock clock) - { - DbContext = dbContext; - Logger.Info("Disabling auto detect changes on this DbContext. Changes will be detected explicitly when flushing."); - DbContext.ChangeTracker.AutoDetectChangesEnabled = false; - IdentityHolder = identityHolder; - Clock = clock; - } - - public void Flush() - { - DetectChanges(); - UpdateTrackingProperties(); - DbContext.TraceChangeTrackerState(); - CheckForMissingTenantIds(); - SaveChanges(); - } - - private void DetectChanges() - { - using (Logger.DebugDuration("Detecting changes")) - { - DbContext.ChangeTracker.DetectChanges(); - } - } - - private void UpdateTrackingProperties() - { - using (Logger.DebugDuration("Updating tracking properties of created and modified entities")) - { - UpdateTrackingProperties(IdentityHolder.Current.Name, Clock.UtcNow); - } - } - - private void CheckForMissingTenantIds() - { - using (Logger.DebugDuration("Checking for missing tenant ids")) - { - AggregateRoot[] aggregatesWithoutTenantId = DbContext - .ChangeTracker - .Entries() - .Where(e => e.State == EntityState.Added) - .Select(e => e.Entity) - .OfType() - .Where(ent => ent.TenantId == 0) - .ToArray(); - if (aggregatesWithoutTenantId.Length > 0) - { - throw new InvalidOperationException( - $"Attempt to save aggregate root entities without tenant id: {string.Join(",", aggregatesWithoutTenantId.Select(agg => agg.DebuggerDisplay))}"); - } - } - } - - private void SaveChanges() - { - using (Logger.DebugDuration("Saving changes")) - { - try - { - DbContext.SaveChanges(); - } - catch (DbUpdateConcurrencyException concurrencyException) - { - throw new ConflictedException("Saving changes failed due to optimistic concurrency violation", concurrencyException); - } - } - } - - private void UpdateTrackingProperties(string userId, DateTime utcNow) - { - userId ??= "anonymous"; - var isTraceEnabled = Logger.IsTraceEnabled(); - var count = 0; - - // Modifying an entity (also removing an entity from an aggregate) should leave the aggregate root as modified - DbContext.ChangeTracker - .Entries() - .Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified || entry.State == EntityState.Deleted) - .Where(entry => !(entry.Entity is AggregateRoot)) - .ToArray() - .ForAll(entry => - { - EntityEntry aggregateRootEntry = GetAggregateRootEntry(DbContext.ChangeTracker, entry); - if (aggregateRootEntry.State == EntityState.Unchanged) aggregateRootEntry.State = EntityState.Modified; - }); - - DbContext.ChangeTracker - .Entries() - .Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified) - .ForAll(entry => - { - try - { - count++; - Entity entity = entry.Entity; - - if (entry.State == EntityState.Added) - { - if (isTraceEnabled) Logger.Trace("tracking that {0}[{1}] was created by {2} at {3:T} UTC", entity.GetType().Name, entity.Id, userId, utcNow); - entity.SetCreatedProperties(userId, utcNow); - } - else if (entry.State == EntityState.Modified) - { - if (isTraceEnabled) Logger.Trace("tracking that {0}[{1}] was modified by {2} at {3:T} UTC", entity.GetType().Name, entity.Id, userId, utcNow); - entity.SetModifiedProperties(userId, utcNow); - - // this line causes the recent changes of tracking properties to be detected before flushing - entry.State = EntityState.Modified; - } - } - catch (Exception ex) - { - Logger.Warn(ex, "Updating tracking properties failed"); - throw; - } - }); - if (count > 0) Logger.Debug($"Tracked {count} entities as created/changed on {utcNow:u} by {userId}"); - } - - /// - /// This method finds the EntityEntry<AggregateRoot> of an EntityEntry<Entity> - /// assuming it has been loaded and is being tracked by the change tracker. - /// - [return: NotNull] - private static EntityEntry GetAggregateRootEntry(ChangeTracker changeTracker, EntityEntry entry) - { - var entityIdentifier = $"{entry.Entity.GetType().Name}[{(entry.Entity as Identified)?.Id}]"; - Logger.Debug($"Searching aggregate root of {entityIdentifier}"); - foreach (NavigationEntry navigation in entry.Navigations) - { - TypeInfo navTargetTypeInfo = navigation.Metadata.TargetEntityType.ClrType.GetTypeInfo(); - int navigationTargetForeignKeyValue; - - if (navigation.CurrentValue == null) - { - var navigationMetadata = ((INavigation)navigation.Metadata); - // orphaned entity, original value contains the foreign key value - if (navigationMetadata.ForeignKey.Properties.Count > 1) throw new InvalidOperationException("Foreign Keys with multiple properties are not supported."); - - IProperty property = navigationMetadata.ForeignKey.Properties[0]; - navigationTargetForeignKeyValue = (int) entry.OriginalValues[property]; - } - else - { - // added or modified entity, current value contains the foreign key value - navigationTargetForeignKeyValue = ((Entity) navigation.CurrentValue).Id; - } - - // assumption: an entity cannot be loaded on its own. Everything on the navigation path starting from the - // aggregate root must have been loaded before, therefore we can find it using the change tracker - var navigationTargetEntry = changeTracker - .Entries() - .Single(ent => Equals(ent.Entity.GetType().GetTypeInfo(), navTargetTypeInfo) - && ent.Property(nameof(Entity.Id)).CurrentValue.Equals(navigationTargetForeignKeyValue)); - - // if the target is AggregateRoot, no (further) recursion is needed - if (typeof(AggregateRoot).GetTypeInfo().IsAssignableFrom(navTargetTypeInfo)) return navigationTargetEntry; - - // recurse in case of "Entity -> Entity -> AggregateRoot" - Logger.Debug("Recursing..."); - return GetAggregateRootEntry(changeTracker, navigationTargetEntry); - } - - throw new InvalidOperationException($"Could not find aggregate root of {entityIdentifier}"); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs deleted file mode 100644 index f7faa48b..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using Backend.Fx.Patterns.DataGeneration; -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - public class SimpleInjectorDataGenerationModule : IModule - { - private readonly Assembly[] _domainAssemblies; - - public SimpleInjectorDataGenerationModule(params Assembly[] domainAssemblies) - { - _domainAssemblies = domainAssemblies; - } - - public void Register(ICompositionRoot compositionRoot) - { - Container container = ((SimpleInjectorCompositionRoot) compositionRoot).Container; - container.Collection.Register(container.GetTypesToRegister(typeof(IDataGenerator), _domainAssemblies)); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs deleted file mode 100644 index 04ce2d1c..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.DataGeneration; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.Patterns.Jobs; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - /// - /// Wires all public domain services to be injected as scoped instances provided by the array of domain assemblies: - /// - s - /// - s - /// - s - /// - s - /// - s - /// - the collections of s - /// - 's - /// - public class SimpleInjectorDomainModule : SimpleInjectorModule - { - private static readonly ILogger Logger = LogManager.Create(); - private readonly Assembly[] _domainAssemblies; - private readonly string _domainAssembliesForLogging; - - public SimpleInjectorDomainModule(params Assembly[] domainAssemblies) - { - _domainAssemblies = domainAssemblies; - _domainAssembliesForLogging = string.Join(",", _domainAssemblies.Select(ass => ass.GetName().Name)); - } - - protected override void Register(Container container, ScopedLifestyle scopedLifestyle) - { - RegisterDomainAndApplicationServices(container); - - RegisterAuthorization(container); - - // all jobs are dynamically registered - foreach (Type jobType in container.GetTypesToRegister(typeof(IJob), _domainAssemblies)) - { - Logger.Debug($"Registering {jobType.Name}"); - container.Register(jobType); - } - - // domain event handlers - foreach (Type domainEventHandlerType in container.GetTypesToRegister(typeof(IDomainEventHandler<>), _domainAssemblies)) - { - Logger.Debug($"Appending {domainEventHandlerType.Name} to list of IDomainEventHandler"); - container.Collection.Append(typeof(IDomainEventHandler<>), domainEventHandlerType); - } - - // integration message handlers - foreach (Type integrationMessageHandlerType in container.GetTypesToRegister(typeof(IIntegrationMessageHandler<>), _domainAssemblies)) - { - Logger.Debug($"Registering {integrationMessageHandlerType.Name}"); - container.Register(integrationMessageHandlerType); - } - } - - private void RegisterDomainAndApplicationServices(Container container) - { - Logger.Debug($"Registering domain and application services from {string.Join(",", _domainAssemblies.Select(ass => ass.GetName().Name))}"); - var serviceRegistrations = container - .GetTypesToRegister(typeof(IDomainService), _domainAssemblies) - .Concat(container.GetTypesToRegister(typeof(IApplicationService), _domainAssemblies)) - .SelectMany(type => - type.GetTypeInfo() - .ImplementedInterfaces - .Where(i => typeof(IDomainService) != i - && typeof(IApplicationService) != i - && (i.Namespace != null && i.Namespace.StartsWith("Backend") - || _domainAssemblies.Contains(i.GetTypeInfo().Assembly))) - .Select(service => new - { - Service = service, - Implementation = type - }) - ); - foreach (var reg in serviceRegistrations) - { - Logger.Debug($"Registering scoped service {reg.Service.Name} with implementation {reg.Implementation.Name}"); - container.Register(reg.Service, reg.Implementation); - } - } - - /// - /// Auto registering all aggregate authorization classes - /// - private void RegisterAuthorization(Container container) - { - Logger.Debug($"Registering authorization services from {_domainAssembliesForLogging}"); - var aggregateRootAuthorizationTypes = container.GetTypesToRegister(typeof(IAggregateAuthorization<>), _domainAssemblies).ToArray(); - foreach (Type aggregateAuthorizationType in aggregateRootAuthorizationTypes) - { - var serviceTypes = aggregateAuthorizationType - .GetTypeInfo() - .ImplementedInterfaces - .Where(impif => impif.GetTypeInfo().IsGenericType - && impif.GenericTypeArguments.Length == 1 - && typeof(AggregateRoot).GetTypeInfo().IsAssignableFrom(impif.GenericTypeArguments[0].GetTypeInfo())); - - foreach (Type serviceType in serviceTypes) - { - Logger.Debug($"Registering scoped authorization service {serviceType.Name} with implementation {aggregateAuthorizationType.Name}"); - container.Register(serviceType, aggregateAuthorizationType); - } - } - } - } -} diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs deleted file mode 100644 index e9331a84..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Reflection; -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - public class SimpleInjectorInfrastructureModule : IInfrastructureModule - { - private readonly Container _container; - - public SimpleInjectorInfrastructureModule(Container container) - { - _container = container; - } - - public void RegisterScoped() where TService : class where TImpl : class, TService - { - _container.Register(); - } - - public void RegisterScoped(Func factory) where TService : class - { - _container.Register(factory); - } - - public void RegisterScoped(Type serviceType, Assembly[] assembliesToScan) - { - _container.Register(serviceType, assembliesToScan); - } - - public void RegisterDecorator() where TService : class where TImpl : class, TService - { - _container.RegisterDecorator(); - } - - public void RegisterScoped(Type serviceType, Type implementationType) - { - _container.Register(serviceType, implementationType); - } - - public void RegisterSingleton() where TService : class where TImpl : class, TService - { - _container.RegisterSingleton(); - } - - public void RegisterInstance(TService instance) where TService : class - { - _container.RegisterInstance(instance); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs deleted file mode 100644 index cd8abf4d..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - public abstract class SimpleInjectorModule : IModule - { - private static readonly ILogger Logger = LogManager.Create(); - - protected abstract void Register(Container container, ScopedLifestyle scopedLifestyle); - - public virtual void Register(ICompositionRoot compositionRoot) - { - Logger.Debug($"Registering {GetType().Name}"); - var simpleInjectorCompositionRoot = (SimpleInjectorCompositionRoot) compositionRoot; - Register(simpleInjectorCompositionRoot.Container, simpleInjectorCompositionRoot.ScopedLifestyle); - } - } -} diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs deleted file mode 100644 index 11fc82c7..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; -using SimpleInjector; -using SimpleInjector.Advanced; -using SimpleInjector.Lifestyles; - -namespace Backend.Fx.SimpleInjectorDependencyInjection -{ - /// - /// Provides a reusable composition root assuming Simple Injector as container - /// - public class SimpleInjectorCompositionRoot : ICompositionRoot - { - private static readonly ILogger Logger = LogManager.Create(); - - private int _scopeSequenceNumber = 1; - /// - /// This constructor creates a composition root that prefers scoped lifestyle - /// - public SimpleInjectorCompositionRoot() - : this(new ScopedLifestyleBehavior(), new AsyncScopedLifestyle()) - {} - - public SimpleInjectorCompositionRoot(ILifestyleSelectionBehavior lifestyleBehavior, ScopedLifestyle scopedLifestyle) - { - Logger.Info("Initializing SimpleInjector"); - ScopedLifestyle = scopedLifestyle; - Container.Options.LifestyleSelectionBehavior = lifestyleBehavior; - Container.Options.DefaultScopedLifestyle = ScopedLifestyle; - InfrastructureModule = new SimpleInjectorInfrastructureModule(Container); - InstanceProvider = new SimpleInjectorInstanceProvider(Container); - - // SimpleInjector 5 needs this to resolve controllers - Container.Options.ResolveUnregisteredConcreteTypes = true; - } - - public Container Container { get; } = new Container(); - - internal ScopedLifestyle ScopedLifestyle { get; } - - public void RegisterModules(params IModule[] modules) - { - foreach (var module in modules) - { - Logger.Info($"Registering {module.GetType().Name}"); - module.Register(this); - } - } - - #region ICompositionRoot implementation - - public void Verify() - { - Logger.Info("container is being verified"); - try - { - Container.Verify(VerificationOption.VerifyAndDiagnose); - } - catch (Exception ex) - { - Logger.Warn(ex, "container configuration invalid"); - throw; - } - } - - - public object GetInstance(Type serviceType) - { - return Container.GetInstance(serviceType); - } - - public IEnumerable GetInstances(Type serviceType) - { - return Container.GetAllInstances(serviceType); - } - - public T GetInstance() where T : class - { - return Container.GetInstance(); - } - - public IEnumerable GetInstances() where T : class - { - return Container.GetAllInstances(); - } - - /// - public IInjectionScope BeginScope() - { - return new SimpleInjectorInjectionScope(Interlocked.Increment(ref _scopeSequenceNumber), AsyncScopedLifestyle.BeginScope(Container)); - } - - public IInstanceProvider InstanceProvider { get; } - - public IInfrastructureModule InfrastructureModule { get; } - - public Scope GetCurrentScope() - { - return ScopedLifestyle.GetCurrentScope(Container); - } - #endregion - - #region IEventHandlerProvider implementation - - /// - public IEnumerable> GetAllEventHandlers() where TDomainEvent : IDomainEvent - { - return Container.GetAllInstances>(); - } - #endregion - - #region IDisposable implementation - public void Dispose() - { - Container?.Dispose(); - } - #endregion - - /// - /// A behavior that defaults to scoped life style for injected instances - /// - private class ScopedLifestyleBehavior : ILifestyleSelectionBehavior - { - public Lifestyle SelectLifestyle(Type implementationType) - { - return Lifestyle.Scoped; - } - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj similarity index 54% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj index 38b170c0..94812262 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj @@ -1,27 +1,28 @@  - netcoreapp3.1 + net5.0 + - - - - + - + + + + diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs similarity index 99% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs index a43d9900..b3a20743 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs @@ -9,13 +9,14 @@ public class ADemoAggregateGenerator : DataGenerator, IDemoDataGenerator public static string Name = "Demo record"; private readonly IRepository _repository; - public override int Priority => 1; public ADemoAggregateGenerator(IRepository repository) { _repository = repository; } + public override int Priority => 1; + protected override void GenerateCore() { _repository.Add(new AnAggregate(_id++, Name)); @@ -29,4 +30,4 @@ protected override bool ShouldRun() return true; } } -} \ No newline at end of file +} diff --git a/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs new file mode 100644 index 00000000..46f7c70b --- /dev/null +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using Backend.Fx.Patterns.EventAggregation.Domain; + +namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain +{ + public class ADomainEvent : IDomainEvent + { + public HashSet HandledBy { get; } = new(); + } +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs similarity index 85% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs index 6f1f7214..1b8e39d4 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs @@ -6,6 +6,7 @@ public class ADomainEventHandler1 : IDomainEventHandler { public void Handle(ADomainEvent domainEvent) { + domainEvent.HandledBy.Add(GetType()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs similarity index 85% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs index e52b0fe5..3eaaa865 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs @@ -6,6 +6,7 @@ public class ADomainEventHandler2 : IDomainEventHandler { public void Handle(ADomainEvent domainEvent) { + domainEvent.HandledBy.Add(GetType()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs similarity index 85% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs index 72bb7c2b..f10673c9 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs @@ -6,6 +6,7 @@ public class ADomainEventHandler3 : IDomainEventHandler { public void Handle(ADomainEvent domainEvent) { + domainEvent.HandledBy.Add(GetType()); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs similarity index 99% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs index d2feebf0..3ad5262c 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs @@ -5,9 +5,11 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDo public interface ITestDomainService : IDomainService { } + public interface IAnotherTestDomainService : IDomainService { } + public class ADomainService : ITestDomainService, IAnotherTestDomainService { } } diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs similarity index 95% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs index fa1ac6d0..facc5be4 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs @@ -7,6 +7,6 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDo public class AJob : IJob { public void Run() - {} + { } } } diff --git a/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/APackage.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/APackage.cs new file mode 100644 index 00000000..1a6f9fd2 --- /dev/null +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/APackage.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.InMemoryPersistence; +using SimpleInjector; +using SimpleInjector.Packaging; + +namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain +{ + public class APackage : IPackage + { + public void RegisterServices(Container container) + { + container.RegisterSingleton(); + container.Register(); + } + } + + + public class InMemoryPersistencePackage : IPackage + { + public void RegisterServices(Container container) + { + // singleton id generator + container.RegisterInstance(new InMemoryEntityIdGenerator()); + + // InMemory Repositories + container.Register(typeof(IRepository<>), typeof(InMemoryRepository<>)); + + // IQueryable is supported, but should be use with caution, since it bypasses authorization + container.Register(typeof(IQueryable<>), typeof(InMemoryQueryable<>)); + + // InMemory Stores + container.Register(typeof(InMemoryStore<>), typeof(InMemoryStore<>)); + container.RegisterInstance(new InMemoryStores()); + } + } +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs similarity index 99% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs index d8d1e2cd..3f6cfc14 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs @@ -9,13 +9,14 @@ public class AProdAggregateGenerator : DataGenerator, IProductiveDataGenerator public static string Name = "Productive record"; private readonly IRepository _repository; - public override int Priority => 1; public AProdAggregateGenerator(IRepository repository) { _repository = repository; } + public override int Priority => 1; + protected override void GenerateCore() { _repository.Add(new AnAggregate(_id++, Name)); @@ -29,4 +30,4 @@ protected override bool ShouldRun() return true; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs similarity index 67% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs index 2ca6e09d..04ef54df 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs @@ -1,9 +1,9 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain { - public interface ISingletonService {} - + public interface ISingletonService + { } + + public class ASingletonService : ISingletonService - { - - } -} \ No newline at end of file + { } +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs similarity index 100% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs similarity index 95% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs index 7b43b787..aa224b77 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs @@ -2,5 +2,6 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain { - public class AnAggregateAuthorization : AllowAll { } + public class AnAggregateAuthorization : AllowAll + { } } diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs similarity index 99% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs index ee4fa719..f8046f3c 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs @@ -5,6 +5,7 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDo public interface ITestApplicationService : IApplicationService { } + public class AnApplicationService : ITestApplicationService { } } diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs similarity index 82% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs index 85cbacb6..6c2af997 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs @@ -6,11 +6,11 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDo [UsedImplicitly] public class AnIntegrationEvent : IntegrationEvent { - public AnIntegrationEvent(int tenantId, int whatever) : base(tenantId) + public AnIntegrationEvent(int whatever) { Whatever = whatever; } public int Whatever { [UsedImplicitly] get; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs similarity index 98% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs index a76d2934..ebaf7914 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs @@ -4,4 +4,4 @@ public class SomeState { public string Value { get; set; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs similarity index 69% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs index 5b01c2a5..6c073d16 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs @@ -1,8 +1,12 @@ using Backend.Fx.NLogLogging; using Backend.Fx.SimpleInjectorDependencyInjection.Tests; using MarcWittke.Xunit.AssemblyFixture; +using Xunit; -[assembly: Xunit.TestFramework("MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", "MarcWittke.Xunit.AssemblyFixture")] +[assembly: + TestFramework( + "MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", + "MarcWittke.Xunit.AssemblyFixture")] [assembly: AssemblyFixture(typeof(TestLoggingFixture))] namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests @@ -12,4 +16,4 @@ public class TestLoggingFixture : LoggingFixture public TestLoggingFixture() : base("Backend.Fx") { } } -} \ No newline at end of file +} diff --git a/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs new file mode 100644 index 00000000..2c8a5c11 --- /dev/null +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs @@ -0,0 +1,29 @@ +using System; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Patterns.EventAggregation.Integration; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests +{ + public class TheMisconfiguredSimpleInjectorCompositionRoot + { + [Fact] + public void ThrowsOnValidation() + { + var sut = new SimpleInjectorCompositionRoot(A.Fake()); + sut.Container.Register(); + Assert.Throws(() => sut.Verify()); + } + + + public class UnresolvableService + { + public UnresolvableService(Entity e) + { + throw new Exception( + $"This constructor should never be called, since the Entity {e?.GetType().Name} cannot be resolved by the container"); + } + } + } +} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs similarity index 61% rename from tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs index 1c83a4bc..c2a3a19c 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs @@ -1,9 +1,11 @@ using System; -using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain; +using FakeItEasy; using SimpleInjector; using Xunit; @@ -12,15 +14,20 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests public class TheSimpleInjectorCompositionRoot : IDisposable { private readonly SimpleInjectorCompositionRoot _sut; - + public TheSimpleInjectorCompositionRoot() { - _sut = new SimpleInjectorCompositionRoot(); - Assembly domainAssembly = typeof(AnAggregate).GetTypeInfo().Assembly; - _sut.RegisterModules(new ADomainModule(domainAssembly)); + var domainAssembly = typeof(AnAggregate).GetTypeInfo().Assembly; + _sut = new SimpleInjectorCompositionRoot(A.Fake(), domainAssembly); + _sut.Container.RegisterPackages(new[] { domainAssembly }); _sut.Verify(); } + public void Dispose() + { + _sut.Dispose(); + } + [Fact] public void ProvidesAutoRegisteredDomainServices() { @@ -55,23 +62,21 @@ public void ProvidesAutoRegisteredApplicationServices() } } - - [Fact] public void ProvidesScopedInstancesWhenScopeHasBeenStarted() { ITestDomainService scope1Instance; ITestDomainService scope2Instance; - using (_sut.BeginScope()) + using (var scope = _sut.BeginScope()) { - scope1Instance = _sut.GetInstance(); + scope1Instance = scope.InstanceProvider.GetInstance(); Assert.NotNull(scope1Instance); } - using (_sut.BeginScope()) + using (var scope = _sut.BeginScope()) { - scope2Instance = _sut.GetInstance(); + scope2Instance = scope.InstanceProvider.GetInstance(); Assert.NotNull(scope2Instance); } @@ -81,28 +86,28 @@ public void ProvidesScopedInstancesWhenScopeHasBeenStarted() [Fact] public void ProvidesSingletonAndScopedInstancesAccordingly() { - const int parallelScopeCount = 1000; - object[] scopedInstances = new object[parallelScopeCount]; - object[] singletonInstances = new object[parallelScopeCount]; - Task[] tasks = new Task[parallelScopeCount]; + var scopedInstances = new object[parallelScopeCount]; + var singletonInstances = new object[parallelScopeCount]; + var tasks = new Task[parallelScopeCount]; var waiter = new ManualResetEvent(false); // resolving a singleton service and a scoped service in a massive parallel scenario - for (int index = 0; index < parallelScopeCount; index++) + for (var index = 0; index < parallelScopeCount; index++) { - var indexClosure = index; - tasks[index] = Task.Factory.StartNew(() => - { - // using the reset event to enforce a maximum grade of parallelism - waiter.WaitOne(); - using (_sut.BeginScope()) + int indexClosure = index; + tasks[index] = Task.Factory.StartNew( + () => { - scopedInstances[indexClosure] = _sut.GetInstance(); - singletonInstances[indexClosure] = _sut.GetInstance(); - } - }); + // using the reset event to enforce a maximum grade of parallelism + waiter.WaitOne(); + using (var scope = _sut.BeginScope()) + { + scopedInstances[indexClosure] = scope.InstanceProvider.GetInstance(); + singletonInstances[indexClosure] = scope.InstanceProvider.GetInstance(); + } + }); } // let the show begin... @@ -110,12 +115,12 @@ public void ProvidesSingletonAndScopedInstancesAccordingly() Task.WaitAll(tasks); // asserting for equality: singleton instances must be equal, scoped instances must be unique - for (int index = 0; index < parallelScopeCount; index++) + for (var index = 0; index < parallelScopeCount; index++) { Assert.NotNull(scopedInstances[index]); Assert.NotNull(singletonInstances[index]); - for (int indexComp = 0; indexComp < parallelScopeCount; indexComp++) + for (var indexComp = 0; indexComp < parallelScopeCount; indexComp++) { if (index != indexComp) { @@ -132,18 +137,16 @@ public void ThrowsWhenScopedInstanceIsRequestedOutsideScope() { Assert.Throws(() => _sut.GetInstance()); Assert.Throws(() => _sut.GetInstance(typeof(ITestDomainService))); - Assert.Null(_sut.GetCurrentScope()); - using (_sut.BeginScope()) + using (var scope = _sut.BeginScope()) { - var sutInstance = _sut.GetInstance(); - var scopeInstance = _sut.GetInstance(); + var sutInstance = scope.InstanceProvider.GetInstance(); + var scopeInstance = scope.InstanceProvider.GetInstance(); Assert.NotNull(sutInstance); Assert.NotNull(scopeInstance); Assert.Equal(sutInstance, scopeInstance); } - Assert.Null(_sut.GetCurrentScope()); Assert.Throws(() => _sut.GetInstance()); Assert.Throws(() => _sut.GetInstance(typeof(ITestDomainService))); } @@ -151,20 +154,18 @@ public void ThrowsWhenScopedInstanceIsRequestedOutsideScope() [Fact] public void CanProvideEventHandlers() { - using (_sut.BeginScope()) + var aDomainEvent = new ADomainEvent(); + + using (var scope = _sut.BeginScope()) { - var handlers = _sut.GetAllEventHandlers().ToArray(); - - // these three handlers should have been auto registered during boot by scanning the assembly - Assert.True(handlers.OfType().Any()); - Assert.True(handlers.OfType().Any()); - Assert.True(handlers.OfType().Any()); + var domainEventAggregator = scope.InstanceProvider.GetInstance(); + domainEventAggregator.PublishDomainEvent(aDomainEvent); + domainEventAggregator.RaiseEvents(); } - } - - public void Dispose() - { - _sut.Dispose(); + + Assert.Contains(typeof(ADomainEventHandler1), aDomainEvent.HandledBy); + Assert.Contains(typeof(ADomainEventHandler2), aDomainEvent.HandledBy); + Assert.Contains(typeof(ADomainEventHandler3), aDomainEvent.HandledBy); } } } diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj similarity index 94% rename from src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj index 9329d9d0..942acb7a 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj @@ -29,7 +29,7 @@ - + \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Properties/AssemblyInfo.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/Properties/AssemblyInfo.cs similarity index 100% rename from src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Properties/AssemblyInfo.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/Properties/AssemblyInfo.cs diff --git a/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs new file mode 100644 index 00000000..0741a4c3 --- /dev/null +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Principal; +using System.Threading; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DataGeneration; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Patterns.Jobs; +using SimpleInjector; +using SimpleInjector.Advanced; +using SimpleInjector.Lifestyles; + +namespace Backend.Fx.SimpleInjectorDependencyInjection +{ + /// + /// Provides a reusable composition root assuming Simple Injector as container + /// + public class SimpleInjectorCompositionRoot : ICompositionRoot + { + private static readonly ILogger Logger = LogManager.Create(); + private readonly Assembly[] _assemblies; + private readonly string _assembliesForLogging; + + private int _scopeSequenceNumber = 1; + + /// + /// This constructor creates a composition root that prefers scoped lifestyle + /// + public SimpleInjectorCompositionRoot(IMessageBus messageBus, params Assembly[] assemblies) + : this(messageBus, assemblies, new ScopedLifestyleBehavior(), new AsyncScopedLifestyle()) + { } + + private SimpleInjectorCompositionRoot( + IMessageBus messageBus, + Assembly[] assemblies, + ILifestyleSelectionBehavior lifestyleBehavior, + ScopedLifestyle scopedLifestyle) + { + Logger.Info("Initializing SimpleInjector"); + + _assemblies = assemblies ?? Array.Empty(); + _assembliesForLogging = string.Join(",", _assemblies.Select(ass => ass.GetName().Name)); + + Container.Options.LifestyleSelectionBehavior = lifestyleBehavior; + Container.Options.DefaultScopedLifestyle = scopedLifestyle; + InstanceProvider = new SimpleInjectorInstanceProvider(Container); + + // the basic types that are open for decorators + Container.Register(); + Container.Register(); + + // holder types that provide access to cross cutting, scope-local state + Container.Register, CurrentTenantIdHolder>(); + Container.Register, CurrentIdentityHolder>(); + Container.Register, CurrentCorrelationHolder>(); + + RegisterDomainAndApplicationServices(Container); + + RegisterAuthorization(Container); + + // all jobs are dynamically registered + foreach (var jobType in Container.GetTypesToRegister(typeof(IJob), _assemblies)) + { + Logger.Debug($"Registering {jobType.Name}"); + Container.Register(jobType); + } + + // domain event aggregator + Container.Register( + () => new DomainEventAggregator(new SimpleInjectorDomainEventHandlerProvider(Container))); + + // domain event handlers + foreach (var domainEventHandlerType in Container.GetTypesToRegister( + typeof(IDomainEventHandler<>), + _assemblies)) + { + Logger.Debug($"Appending {domainEventHandlerType.Name} to list of IDomainEventHandler"); + Container.Collection.Append(typeof(IDomainEventHandler<>), domainEventHandlerType); + } + + // integration event message bus scope + Container.Register( + () => new MessageBusScope( + messageBus, + Container.GetInstance>(), + Container.GetInstance>())); + + // integration message handlers + foreach (var integrationMessageHandlerType in Container.GetTypesToRegister( + typeof(IIntegrationMessageHandler<>), + _assemblies)) + { + Logger.Debug($"Registering {integrationMessageHandlerType.Name}"); + Container.Register(integrationMessageHandlerType); + } + + // data generators + Container.Collection.Register( + Container.GetTypesToRegister(typeof(IDataGenerator), _assemblies)); + } + + public Container Container { get; } = new Container(); + + #region IDisposable implementation + + public void Dispose() + { + Container?.Dispose(); + } + + #endregion + + private void RegisterDomainAndApplicationServices(Container container) + { + Logger.Debug( + $"Registering domain and application services from {string.Join(",", _assemblies.Select(ass => ass.GetName().Name))}"); + var serviceRegistrations = container + .GetTypesToRegister(typeof(IDomainService), _assemblies) + .Concat(container.GetTypesToRegister(typeof(IApplicationService), _assemblies)) + .SelectMany( + type => + type.GetTypeInfo() + .ImplementedInterfaces + .Where( + i => typeof(IDomainService) != i + && typeof(IApplicationService) != i + && (i.Namespace != null && i.Namespace.StartsWith("Backend") + || _assemblies.Contains(i.GetTypeInfo().Assembly))) + .Select( + service => new + { + Service = service, + Implementation = type + })); + foreach (var reg in serviceRegistrations) + { + Logger.Debug( + $"Registering scoped service {reg.Service.Name} with implementation {reg.Implementation.Name}"); + container.Register(reg.Service, reg.Implementation); + } + } + + /// + /// Auto registering all aggregate authorization classes + /// + private void RegisterAuthorization(Container container) + { + Logger.Debug($"Registering authorization services from {_assembliesForLogging}"); + Type[] aggregateRootAuthorizationTypes + = container.GetTypesToRegister(typeof(IAggregateAuthorization<>), _assemblies).ToArray(); + foreach (var aggregateAuthorizationType in aggregateRootAuthorizationTypes) + { + IEnumerable serviceTypes = aggregateAuthorizationType + .GetTypeInfo() + .ImplementedInterfaces + .Where( + implInterface => implInterface.GetTypeInfo().IsGenericType + && implInterface.GenericTypeArguments.Length == 1 + && typeof(AggregateRoot).GetTypeInfo() + .IsAssignableFrom(implInterface.GenericTypeArguments[0].GetTypeInfo())); + + foreach (var serviceType in serviceTypes) + { + Logger.Debug( + $"Registering scoped authorization service {serviceType.Name} with implementation {aggregateAuthorizationType.Name}"); + container.Register(serviceType, aggregateAuthorizationType); + } + } + } + + + private class SimpleInjectorDomainEventHandlerProvider : IDomainEventHandlerProvider + { + private readonly Container _container; + + public SimpleInjectorDomainEventHandlerProvider(Container container) + { + _container = container; + } + + public IEnumerable> GetAllEventHandlers() + where TDomainEvent : IDomainEvent + { + return _container.GetAllInstances>(); + } + } + + + /// + /// A behavior that defaults to scoped life style for injected instances + /// + private class ScopedLifestyleBehavior : ILifestyleSelectionBehavior + { + public Lifestyle SelectLifestyle(Type implementationType) + { + return Lifestyle.Scoped; + } + } + + + #region ICompositionRoot implementation + + public void Verify() + { + Logger.Info("container is being verified"); + try + { + Container.Verify(VerificationOption.VerifyAndDiagnose); + } + catch (Exception ex) + { + Logger.Warn(ex, "container configuration invalid"); + throw; + } + } + + public object GetInstance(Type serviceType) + { + return Container.GetInstance(serviceType); + } + + public IEnumerable GetInstances(Type serviceType) + { + return Container.GetAllInstances(serviceType); + } + + public T GetInstance() where T : class + { + return Container.GetInstance(); + } + + public IEnumerable GetInstances() where T : class + { + return Container.GetAllInstances(); + } + + /// + public IInjectionScope BeginScope() + { + return new SimpleInjectorInjectionScope( + Interlocked.Increment(ref _scopeSequenceNumber), + AsyncScopedLifestyle.BeginScope(Container)); + } + + public IInstanceProvider InstanceProvider { get; } + + #endregion + } +} diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs similarity index 99% rename from src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs index c22eedc3..2a1d7190 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs @@ -15,10 +15,9 @@ public SimpleInjectorInjectionScope(int sequenceNumber, Scope scope) : base(sequ public override IInstanceProvider InstanceProvider { get; } - public override void Dispose() { _scope.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs similarity index 89% rename from src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs rename to src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs index 610dd71b..5d467eb2 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs +++ b/src/implementations/dependencyinjection/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs @@ -22,7 +22,7 @@ public object GetInstance(Type serviceType) public IEnumerable GetInstances(Type serviceType) { - return (IEnumerable) _container.GetInstance(typeof(IEnumerable<>).MakeGenericType(serviceType)); + return (IEnumerable)_container.GetInstance(typeof(IEnumerable<>).MakeGenericType(serviceType)); } public T GetInstance() where T : class @@ -35,4 +35,4 @@ public IEnumerable GetInstances() where T : class return _container.GetInstance>(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj similarity index 74% rename from tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj index 8e6347f4..59dd5a84 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 false @@ -19,8 +19,8 @@ - - + + diff --git a/tests/Backend.Fx.RabbitMq.Tests/TestConfig.cs b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TestConfig.cs similarity index 66% rename from tests/Backend.Fx.RabbitMq.Tests/TestConfig.cs rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TestConfig.cs index 934832c9..aba51e18 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TestConfig.cs +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TestConfig.cs @@ -3,7 +3,10 @@ using MarcWittke.Xunit.AssemblyFixture; using Xunit; -[assembly: TestFramework("MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", "MarcWittke.Xunit.AssemblyFixture")] +[assembly: + TestFramework( + "MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", + "MarcWittke.Xunit.AssemblyFixture")] [assembly: AssemblyFixture(typeof(TestLoggingFixture))] namespace Backend.Fx.RabbitMq.Tests @@ -11,7 +14,6 @@ namespace Backend.Fx.RabbitMq.Tests public class TestLoggingFixture : LoggingFixture { public TestLoggingFixture() : base("Backend.Fx") - { - } + { } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs similarity index 61% rename from tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs index 0a9a5280..8a5c25c0 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs @@ -10,17 +10,16 @@ namespace Backend.Fx.RabbitMq.Tests { public class TheRabbitMqChannel { - private readonly ITestOutputHelper _testOutputHelper; - - private readonly AutoResetEvent _shutdown = new AutoResetEvent(false); - - private readonly ConnectionFactory _factory = new ConnectionFactory + private readonly ConnectionFactory _factory = new() { HostName = "localhost", UserName = "anicors", Password = "R4bb!tMQ" }; + private readonly AutoResetEvent _shutdown = new(false); + private readonly ITestOutputHelper _testOutputHelper; + public TheRabbitMqChannel(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; @@ -32,81 +31,81 @@ public void CanSendAndReceive() var receivingChannel = new ReceivingChannel(_testOutputHelper, _factory); receivingChannel.Connect(); - var sendingChannel = new SendingChannel(_testOutputHelper, _factory); + var sendingChannel = new SendingChannel(_factory); sendingChannel.Connect(); - + sendingChannel.Send("gnarf", "whatever"); Assert.False(receivingChannel.Received.WaitOne(1000)); - + sendingChannel.Send("info", "whatever"); Assert.True(receivingChannel.Received.WaitOne(1000)); - + _shutdown.Set(); } + private class SendingChannel { - private readonly ITestOutputHelper _testOutputHelper; private readonly ConnectionFactory _connectionFactory; - private IConnection _connection; private IModel _channel; + private IConnection _connection; - public SendingChannel(ITestOutputHelper testOutputHelper, ConnectionFactory connectionFactory) + public SendingChannel(ConnectionFactory connectionFactory) { - _testOutputHelper = testOutputHelper; _connectionFactory = connectionFactory; } - + public void Connect() { _connection = _connectionFactory.CreateConnection(); _channel = _connection.CreateModel(); - _channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); + _channel.ExchangeDeclare("direct_logs", "direct"); } public void Send(string severity, string message) { - var body = Encoding.UTF8.GetBytes(message); - _channel.BasicPublish(exchange: "direct_logs", routingKey: severity, basicProperties: null, body: body); + byte[] body = Encoding.UTF8.GetBytes(message); + _channel.BasicPublish("direct_logs", severity, null, body); } } - + + private class ReceivingChannel { - private readonly ITestOutputHelper _testOutputHelper; private readonly ConnectionFactory _connectionFactory; - private IConnection _connection; + private readonly ITestOutputHelper _testOutputHelper; private IModel _channel; - public AutoResetEvent Received { get; } = new AutoResetEvent(false); - + private IConnection _connection; + public ReceivingChannel(ITestOutputHelper testOutputHelper, ConnectionFactory connectionFactory) { _testOutputHelper = testOutputHelper; _connectionFactory = connectionFactory; } + public AutoResetEvent Received { get; } = new(false); + public void Connect() { _connection = _connectionFactory.CreateConnection(); _channel = _connection.CreateModel(); - _channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); + _channel.ExchangeDeclare("direct_logs", "direct"); _channel.QueueDeclare("myqueue", true, false, false, null); - _channel.QueueBind(queue: "myqueue", exchange: "direct_logs", routingKey: "info"); + _channel.QueueBind("myqueue", "direct_logs", "info"); _testOutputHelper.WriteLine(" [*] Waiting for messages."); var consumer = new EventingBasicConsumer(_channel); - consumer.Received += (model, ea) => - { - var body = ea.Body.ToArray(); - var message = Encoding.UTF8.GetString(body); - var routingKey = ea.RoutingKey; - _testOutputHelper.WriteLine($" [x] Received '{routingKey}':'{message}'"); - Received.Set(); - _channel.BasicAck(ea.DeliveryTag, false); - }; - _channel.BasicConsume(queue: "myqueue", autoAck: false, consumer: consumer); + consumer.Received += (_, ea) => + { + byte[] body = ea.Body.ToArray(); + string message = Encoding.UTF8.GetString(body); + string routingKey = ea.RoutingKey; + _testOutputHelper.WriteLine($" [x] Received '{routingKey}':'{message}'"); + Received.Set(); + _channel.BasicAck(ea.DeliveryTag, false); + }; + _channel.BasicConsume("myqueue", false, consumer); } } - } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs similarity index 66% rename from tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index bfbe88e7..fabf2c77 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -11,9 +11,9 @@ namespace Backend.Fx.RabbitMq.Tests { public class TheRabbitMqMessageBus { - private readonly ManualResetEvent _received = new ManualResetEvent(false); - private readonly BackendFxApplicationInvoker _senderInvoker; + private readonly ManualResetEvent _received = new(false); private readonly BackendFxApplicationInvoker _receiverInvoker; + private readonly BackendFxApplicationInvoker _senderInvoker; public TheRabbitMqMessageBus() { @@ -27,28 +27,37 @@ public TheRabbitMqMessageBus() var fakeInstanceProvider = A.Fake(); A.CallTo(() => fakeReceiverApplication.CompositionRoot.BeginScope()).Returns(fakeScope); A.CallTo(() => fakeScope.InstanceProvider).Returns(fakeInstanceProvider); - A.CallTo(() => fakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(TestIntegrationEventHandler)))) - .Returns(new TestIntegrationEventHandler(_received)); + A.CallTo( + () => fakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(TestIntegrationEventHandler)))) + .Returns(new TestIntegrationEventHandler(_received)); } //[Fact] public void CanBeUsedWithBackendFxApplication() { - IMessageBus sender = new RabbitMqMessageBus(new ConnectionFactory - { - HostName = "localhost", - UserName = "anicors", - Password = "R4bb!tMQ" - }, 5, "unittest", "testSender"); + IMessageBus sender = new RabbitMqMessageBus( + new ConnectionFactory + { + HostName = "localhost", + UserName = "anicors", + Password = "R4bb!tMQ" + }, + 5, + "unittest", + "testSender"); sender.ProvideInvoker(_senderInvoker); sender.Connect(); - var receiver = new RabbitMqMessageBus(new ConnectionFactory - { - HostName = "localhost", - UserName = "anicors", - Password = "R4bb!tMQ" - }, 5, "unittest", "testReceiver"); + var receiver = new RabbitMqMessageBus( + new ConnectionFactory + { + HostName = "localhost", + UserName = "anicors", + Password = "R4bb!tMQ" + }, + 5, + "unittest", + "testReceiver"); receiver.ProvideInvoker(_receiverInvoker); receiver.Connect(); @@ -58,6 +67,7 @@ public void CanBeUsedWithBackendFxApplication() Assert.True(_received.WaitOne(Debugger.IsAttached ? int.MaxValue : 5000)); } + public class TestIntegrationEventHandler : IIntegrationMessageHandler { private readonly ManualResetEvent _received; @@ -73,14 +83,15 @@ public void Handle(TestIntegrationEvent eventData) } } + public class TestIntegrationEvent : IntegrationEvent { - public TestIntegrationEvent(int sequencenumber) : base(999) + public TestIntegrationEvent(int sequenceNumber) { - Sequencenumber = sequencenumber; + SequenceNumber = sequenceNumber; } - public int Sequencenumber { get; } + public int SequenceNumber { get; } } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj similarity index 94% rename from src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj index 4a22d8f5..c505569a 100644 --- a/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj @@ -31,7 +31,7 @@ - + diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/Properties/AssemblyInfo.cs similarity index 74% rename from src/implementations/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/Properties/AssemblyInfo.cs index 5f565d7d..43603728 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyVersion("0.0.0.0")] [assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/RabbitMQChannel.cs similarity index 70% rename from src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/RabbitMQChannel.cs index cae23410..cd252c88 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/RabbitMQChannel.cs @@ -15,20 +15,25 @@ namespace Backend.Fx.RabbitMq public class RabbitMqChannel : IDisposable { private static readonly ILogger Logger = LogManager.Create(); + private readonly IConnectionFactory _connectionFactory; private readonly string _exchangeName; private readonly IMessageNameProvider _messageNameProvider; - private readonly IConnectionFactory _connectionFactory; private readonly string _queueName; private readonly int _retryCount; private readonly HashSet _subscribedEventNames = new HashSet(); private readonly object _syncRoot = new object(); + private IModel _channel; private IConnection _connection; private EventingBasicConsumer _consumer; private bool _isDisposed; - private IModel _channel; - public RabbitMqChannel(IMessageNameProvider messageNameProvider, IConnectionFactory connectionFactory, string exchangeName, string queueName, int retryCount) + public RabbitMqChannel( + IMessageNameProvider messageNameProvider, + IConnectionFactory connectionFactory, + string exchangeName, + string queueName, + int retryCount) { _messageNameProvider = messageNameProvider; _connectionFactory = connectionFactory; @@ -39,7 +44,10 @@ public RabbitMqChannel(IMessageNameProvider messageNameProvider, IConnectionFact public void Dispose() { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } _isDisposed = true; @@ -64,7 +72,10 @@ public void EnsureClosed() if (_channel != null) { _channel.CallbackException -= OnCallbackException; - if (_channel.IsOpen) _channel.Close(); + if (_channel.IsOpen) + { + _channel.Close(); + } _channel.Dispose(); _channel = null; @@ -75,7 +86,10 @@ public void EnsureClosed() _connection.ConnectionShutdown -= OnConnectionShutdown; _connection.CallbackException -= OnCallbackException; _connection.ConnectionBlocked -= OnConnectionBlocked; - if (_connection.IsOpen) _connection.Close(); + if (_connection.IsOpen) + { + _connection.Close(); + } _connection.Dispose(); _connection = null; @@ -102,12 +116,12 @@ private bool Open() Logger.Info("RabbitMQ Client is trying to connect"); Policy.Handle() - .Or() - .WaitAndRetry(_retryCount, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (ex, time) => { Logger.Warn(ex); } - ) - .Execute(() => { _connection = _connectionFactory.CreateConnection(); }); + .Or() + .WaitAndRetry( + _retryCount, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => { Logger.Warn(ex); }) + .Execute(() => { _connection = _connectionFactory.CreateConnection(); }); if (_connection?.IsOpen == true) { @@ -115,7 +129,8 @@ private bool Open() _connection.CallbackException += OnCallbackException; _connection.ConnectionBlocked += OnConnectionBlocked; - Logger.Info($"Acquired a connection to RabbitMQ host {_connection.Endpoint.HostName} and is subscribed to failure events"); + Logger.Info( + $"Acquired a connection to RabbitMQ host {_connection.Endpoint.HostName} and is subscribed to failure events"); _channel = _connection.CreateModel(); _channel.ExchangeDeclare(_exchangeName, "direct"); @@ -125,9 +140,10 @@ private bool Open() _channel.BasicConsume(_queueName, false, _consumer); _channel.CallbackException += OnCallbackException; - foreach (var subscribedEventName in _subscribedEventNames) + foreach (string subscribedEventName in _subscribedEventNames) { - Logger.Info($"Binding messages on exchange {_exchangeName} with routing key {subscribedEventName} to queue {_queueName}"); + Logger.Info( + $"Binding messages on exchange {_exchangeName} with routing key {subscribedEventName} to queue {_queueName}"); _channel.QueueBind(_queueName, _exchangeName, subscribedEventName); } @@ -141,11 +157,11 @@ private bool Open() public void PublishEvent(IIntegrationEvent integrationEvent) { - var messageName = _messageNameProvider.GetMessageName(integrationEvent); - var message = JsonConvert.SerializeObject(integrationEvent); - var body = Encoding.UTF8.GetBytes(message); + string messageName = _messageNameProvider.GetMessageName(integrationEvent); + string message = JsonConvert.SerializeObject(integrationEvent); + byte[] body = Encoding.UTF8.GetBytes(message); - DoResilent(() => _channel.BasicPublish(_exchangeName, messageName, null, body)); + DoResilient(() => _channel.BasicPublish(_exchangeName, messageName, null, body)); } public void Subscribe(string messageName) @@ -165,18 +181,21 @@ public void Unsubscribe(string eventName) public void Acknowledge(ulong deliveryTag) { Logger.Debug($"Acknowledging {deliveryTag}"); - DoResilent(() => _channel.BasicAck(deliveryTag, false)); + DoResilient(() => _channel.BasicAck(deliveryTag, false)); } public void NAcknowledge(ulong deliveryTag) { Logger.Debug($"NAcknowledging {deliveryTag}"); - DoResilent(() => _channel.BasicNack(deliveryTag, false, false)); + DoResilient(() => _channel.BasicNack(deliveryTag, false, false)); } private void OnCallbackException(object sender, CallbackExceptionEventArgs e) { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } Logger.Warn(e.Exception, "A RabbitMQ connection threw an exception."); Open(); @@ -184,7 +203,10 @@ private void OnCallbackException(object sender, CallbackExceptionEventArgs e) private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } Logger.Warn($"A RabbitMQ connection is blocked with reason {e.Reason}"); Open(); @@ -192,7 +214,10 @@ private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) private void OnConnectionShutdown(object sender, ShutdownEventArgs reason) { - if (_isDisposed) return; + if (_isDisposed) + { + return; + } Logger.Warn($"A RabbitMQ connection is shut down with reason {reason}."); Open(); @@ -203,14 +228,15 @@ private void OnMessageReceived(object sender, BasicDeliverEventArgs basicDeliver MessageReceived?.Invoke(this, basicDeliverEventArgs); } - private void DoResilent(Action action) + private void DoResilient(Action action) { Policy.Handle() - .Or() - .WaitAndRetry(_retryCount, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (ex, time) => { Logger.Warn(ex.ToString()); }) - .Execute(action); + .Or() + .WaitAndRetry( + _retryCount, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => { Logger.Warn(ex.ToString()); }) + .Execute(action); } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs similarity index 82% rename from src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs rename to src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs index c53b6f05..c650f229 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs +++ b/src/implementations/integrationeventaggregation/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs @@ -16,9 +16,18 @@ public class RabbitMqMessageBus : MessageBus private static readonly ILogger Logger = LogManager.Create(); private readonly RabbitMqChannel _channel; - public RabbitMqMessageBus(IConnectionFactory connectionFactory, int retryCount, string exchangeName, string receiveQueueName) + public RabbitMqMessageBus( + IConnectionFactory connectionFactory, + int retryCount, + string exchangeName, + string receiveQueueName) { - _channel = new RabbitMqChannel(MessageNameProvider, connectionFactory, exchangeName, receiveQueueName, retryCount); + _channel = new RabbitMqChannel( + MessageNameProvider, + connectionFactory, + exchangeName, + receiveQueueName, + retryCount); } public override void Connect() @@ -70,6 +79,7 @@ protected override void Unsubscribe(string messageName) protected override void Dispose(bool disposing) { if (disposing) + { if (_channel != null) { Logger.Info("Closing RabbitMQ channel..."); @@ -77,10 +87,12 @@ protected override void Dispose(bool disposing) _channel.Dispose(); Logger.Info("RabbitMQ channel closed"); } + } base.Dispose(disposing); } + private class RabbitMqEventProcessingContext : EventProcessingContext { private readonly string _jsonString; @@ -88,10 +100,15 @@ private class RabbitMqEventProcessingContext : EventProcessingContext public RabbitMqEventProcessingContext(object rawReceivedMessage) { Logger.Trace($"Deserializing a message of type {rawReceivedMessage?.GetType().Name ?? "???"}"); - if (!(rawReceivedMessage is byte[] rawEventPayloadBytes)) throw new InvalidOperationException("Raw event payload is not a binary JSON string"); + if (!(rawReceivedMessage is byte[] rawEventPayloadBytes)) + { + throw new InvalidOperationException("Raw event payload is not a binary JSON string"); + } _jsonString = Encoding.UTF8.GetString(rawEventPayloadBytes); - var eventStub = JsonConvert.DeserializeAnonymousType(_jsonString, new {tenantId = 0, correlationId = Guid.Empty}); + var eventStub = JsonConvert.DeserializeAnonymousType( + _jsonString, + new { tenantId = 0, correlationId = Guid.Empty }); TenantId = new TenantId(eventStub.tenantId); CorrelationId = eventStub.correlationId; } @@ -99,12 +116,13 @@ public RabbitMqEventProcessingContext(object rawReceivedMessage) public override TenantId TenantId { get; } public override dynamic DynamicEvent => JObject.Parse(_jsonString); + public override Guid CorrelationId { get; } public override IIntegrationEvent GetTypedEvent(Type eventType) { - return (IIntegrationEvent) JsonConvert.DeserializeObject(_jsonString, eventType); + return (IIntegrationEvent)JsonConvert.DeserializeObject(_jsonString, eventType); } } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.Log4NetLogging/Backend.Fx.Log4NetLogging.csproj b/src/implementations/logging/Backend.Fx.Log4NetLogging/Backend.Fx.Log4NetLogging.csproj similarity index 83% rename from src/implementations/Backend.Fx.Log4NetLogging/Backend.Fx.Log4NetLogging.csproj rename to src/implementations/logging/Backend.Fx.Log4NetLogging/Backend.Fx.Log4NetLogging.csproj index f147fe52..e4361225 100644 --- a/src/implementations/Backend.Fx.Log4NetLogging/Backend.Fx.Log4NetLogging.csproj +++ b/src/implementations/logging/Backend.Fx.Log4NetLogging/Backend.Fx.Log4NetLogging.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/implementations/Backend.Fx.Log4NetLogging/Log4NetLogger.cs b/src/implementations/logging/Backend.Fx.Log4NetLogging/Log4NetLogger.cs similarity index 91% rename from src/implementations/Backend.Fx.Log4NetLogging/Log4NetLogger.cs rename to src/implementations/logging/Backend.Fx.Log4NetLogging/Log4NetLogger.cs index 0da8baf0..ec55c97a 100644 --- a/src/implementations/Backend.Fx.Log4NetLogging/Log4NetLogger.cs +++ b/src/implementations/logging/Backend.Fx.Log4NetLogging/Log4NetLogger.cs @@ -2,10 +2,12 @@ using Backend.Fx.Logging; using log4net; using log4net.Core; +using log4net.Repository.Hierarchy; +using ILogger = Backend.Fx.Logging.ILogger; namespace Backend.Fx.Log4NetLogging { - public class Log4NetLogger : Logging.ILogger + public class Log4NetLogger : ILogger { private readonly ILog _log4NetLogger; @@ -14,6 +16,8 @@ internal Log4NetLogger(ILog log4NetLogger) _log4NetLogger = log4NetLogger; } + private Level Level => ((Logger)_log4NetLogger.Logger).EffectiveLevel; + public Exception Fatal(Exception exception) { _log4NetLogger.Fatal(exception); @@ -71,11 +75,6 @@ public Exception Info(Exception exception) return exception; } - public void Info(string message) - { - _log4NetLogger.Info(message); - } - public IDisposable InfoDuration(string activity) { return new DurationLogger(Info, activity); @@ -99,8 +98,9 @@ public Exception Info(Exception exception, string format, params object[] args) public bool IsDebugEnabled() { - Level myLevel = Level; - return myLevel == Level.Debug || myLevel == Level.Fine || myLevel == Level.Trace || myLevel == Level.Finer || myLevel == Level.Verbose || myLevel == Level.Finest; + var myLevel = Level; + return myLevel == Level.Debug || myLevel == Level.Fine || myLevel == Level.Trace || + myLevel == Level.Finer || myLevel == Level.Verbose || myLevel == Level.Finest; } public Exception Debug(Exception exception) @@ -109,11 +109,6 @@ public Exception Debug(Exception exception) return exception; } - public void Debug(string message) - { - _log4NetLogger.Debug(message); - } - public IDisposable DebugDuration(string activity) { return new DurationLogger(Debug, activity); @@ -137,8 +132,9 @@ public Exception Debug(Exception exception, string format, params object[] args) public bool IsTraceEnabled() { - Level myLevel = Level; - return myLevel == Level.Trace || myLevel == Level.Finer || myLevel == Level.Verbose || myLevel == Level.Finest; + var myLevel = Level; + return myLevel == Level.Trace || myLevel == Level.Finer || myLevel == Level.Verbose || + myLevel == Level.Finest; } public Exception Trace(Exception exception) @@ -147,11 +143,6 @@ public Exception Trace(Exception exception) return exception; } - public void Trace(string message) - { - _log4NetLogger.Logger.Log(null, Level.Trace, message, null); - } - public IDisposable TraceDuration(string activity) { return new DurationLogger(Trace, activity); @@ -173,6 +164,19 @@ public Exception Trace(Exception exception, string format, params object[] args) return exception; } - private Level Level => ((log4net.Repository.Hierarchy.Logger)_log4NetLogger.Logger).EffectiveLevel; + public void Info(string message) + { + _log4NetLogger.Info(message); + } + + public void Debug(string message) + { + _log4NetLogger.Debug(message); + } + + public void Trace(string message) + { + _log4NetLogger.Logger.Log(null, Level.Trace, message, null); + } } } diff --git a/src/implementations/Backend.Fx.Log4NetLogging/Log4NetLoggerFactory.cs b/src/implementations/logging/Backend.Fx.Log4NetLogging/Log4NetLoggerFactory.cs similarity index 70% rename from src/implementations/Backend.Fx.Log4NetLogging/Log4NetLoggerFactory.cs rename to src/implementations/logging/Backend.Fx.Log4NetLogging/Log4NetLoggerFactory.cs index f341fe2b..873b8d29 100644 --- a/src/implementations/Backend.Fx.Log4NetLogging/Log4NetLoggerFactory.cs +++ b/src/implementations/logging/Backend.Fx.Log4NetLogging/Log4NetLoggerFactory.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; using System.Reflection; +using log4net; using log4net.Repository; +using log4net.Repository.Hierarchy; using BackendFxILogger = Backend.Fx.Logging.ILogger; using BackendFxILoggerFactory = Backend.Fx.Logging.ILoggerFactory; @@ -14,20 +16,20 @@ public class Log4NetLoggerFactory : BackendFxILoggerFactory public Log4NetLoggerFactory() { - _loggerRepository = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy)); - + _loggerRepository = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(Hierarchy)); + BeginActivity(0); } public BackendFxILogger Create(string s) { - return new Log4NetLogger(log4net.LogManager.GetLogger(_loggerRepository.Name, s)); + return new Log4NetLogger(LogManager.GetLogger(_loggerRepository.Name, s)); } public BackendFxILogger Create(Type t) { string s = t.FullName; - var indexOf = s?.IndexOf('[') ?? 0; + int indexOf = s?.IndexOf('[') ?? 0; if (indexOf > 0) { s = s?.Substring(0, indexOf); @@ -43,12 +45,12 @@ public BackendFxILogger Create() public void BeginActivity(int activityIndex) { - log4net.LogicalThreadContext.Properties["app-Activity"] = activityIndex; + LogicalThreadContext.Properties["app-Activity"] = activityIndex; } public void Shutdown() { - log4net.LogManager.Flush(10000); + LogManager.Flush(10000); } } } diff --git a/src/implementations/Backend.Fx.NLogLogging/Backend.Fx.NLogLogging.csproj b/src/implementations/logging/Backend.Fx.NLogLogging/Backend.Fx.NLogLogging.csproj similarity index 94% rename from src/implementations/Backend.Fx.NLogLogging/Backend.Fx.NLogLogging.csproj rename to src/implementations/logging/Backend.Fx.NLogLogging/Backend.Fx.NLogLogging.csproj index 4ad5f9fb..120885a6 100644 --- a/src/implementations/Backend.Fx.NLogLogging/Backend.Fx.NLogLogging.csproj +++ b/src/implementations/logging/Backend.Fx.NLogLogging/Backend.Fx.NLogLogging.csproj @@ -30,7 +30,7 @@ - + \ No newline at end of file diff --git a/src/implementations/Backend.Fx.NLogLogging/Configurations.cs b/src/implementations/logging/Backend.Fx.NLogLogging/Configurations.cs similarity index 67% rename from src/implementations/Backend.Fx.NLogLogging/Configurations.cs rename to src/implementations/logging/Backend.Fx.NLogLogging/Configurations.cs index 7ac3c822..b9b22dd0 100644 --- a/src/implementations/Backend.Fx.NLogLogging/Configurations.cs +++ b/src/implementations/logging/Backend.Fx.NLogLogging/Configurations.cs @@ -4,7 +4,6 @@ using System.Linq; using NLog; using NLog.Config; -using NLog.Layouts; using NLog.Targets; namespace Backend.Fx.NLogLogging @@ -13,18 +12,22 @@ public static class Configurations { private static readonly object SyncLock = new object(); - public static void ForTests(string appRootNamespace, string logfilename = "tests.xlog") + public static void ForTests(string appRootNamespace, string logFileName = "tests.log") { lock (SyncLock) { - if (LogManager.Configuration != null) return; + if (LogManager.Configuration != null) + { + return; + } Logging.LogManager.Initialize(new NLogLoggerFactory()); var config = new LoggingConfiguration(); var consoleTarget = new ConsoleTarget { - Layout = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" + Layout + = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" }; config.AddTarget("console", consoleTarget); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Warn, consoleTarget)); @@ -33,8 +36,9 @@ public static void ForTests(string appRootNamespace, string logfilename = "tests var fileTarget = new FileTarget { DeleteOldFileOnStartup = false, - FileName = @"${basedir}/" + logfilename, - Layout = new Log4JXmlEventLayout(), + FileName = @"${basedir}/" + logFileName, + Layout + = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}", MaxArchiveFiles = 1, ArchiveAboveSize = 10 * 1024 * 1024 }; @@ -42,33 +46,38 @@ public static void ForTests(string appRootNamespace, string logfilename = "tests config.LoggingRules.Add(new LoggingRule(appRootNamespace + ".*", LogLevel.Debug, fileTarget)); config.LoggingRules.Add(new LoggingRule("Xunit.*", LogLevel.Info, fileTarget)); config.LoggingRules.Add(new LoggingRule("Microsoft.*", LogLevel.Warn, fileTarget)); - config.LoggingRules.Add(new LoggingRule("Microsoft.AspNetCore.Hosting.Internal.WebHost", LogLevel.Info, fileTarget)); + config.LoggingRules.Add( + new LoggingRule("Microsoft.AspNetCore.Hosting.Internal.WebHost", LogLevel.Info, fileTarget)); config.LoggingRules.Add(new LoggingRule("Backend.*", LogLevel.Info, fileTarget)); var fatals = new MemoryTarget(LogLevel.Fatal.ToString()) { - Layout = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" + Layout + = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" }; config.AddTarget(fatals); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, LogLevel.Fatal, fatals)); var errors = new MemoryTarget(LogLevel.Error.ToString()) { - Layout = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" + Layout + = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" }; config.AddTarget(errors); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Error, LogLevel.Error, errors)); var warnings = new MemoryTarget(LogLevel.Warn.ToString()) { - Layout = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" + Layout + = @"${level:uppercase=true:padding=5} ${mdc:item=Activity} ${time} ${logger} ${message} ${exception}" }; config.AddTarget(warnings); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Warn, LogLevel.Warn, warnings)); LogManager.Configuration = config; - Console.WriteLine($"Test console shows only warn, error and fatal events. Full log file is available at {Path.Combine(AppContext.BaseDirectory, logfilename)}"); + Console.WriteLine( + $"Test console shows only warn, error and fatal events. Full log file is available at {Path.Combine(AppContext.BaseDirectory, logFileName)}"); } } @@ -82,4 +91,4 @@ public static IEnumerable LogMessages(string level) return LogManager.Configuration?.FindTargetByName(level)?.Logs ?? Enumerable.Empty(); } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.NLogLogging/LoggingFixture.cs b/src/implementations/logging/Backend.Fx.NLogLogging/LoggingFixture.cs similarity index 99% rename from src/implementations/Backend.Fx.NLogLogging/LoggingFixture.cs rename to src/implementations/logging/Backend.Fx.NLogLogging/LoggingFixture.cs index 12278692..17a66171 100644 --- a/src/implementations/Backend.Fx.NLogLogging/LoggingFixture.cs +++ b/src/implementations/logging/Backend.Fx.NLogLogging/LoggingFixture.cs @@ -25,4 +25,4 @@ public void Dispose() NLog.LogManager.Shutdown(); } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.NLogLogging/NLogLogger.cs b/src/implementations/logging/Backend.Fx.NLogLogging/NLogLogger.cs similarity index 93% rename from src/implementations/Backend.Fx.NLogLogging/NLogLogger.cs rename to src/implementations/logging/Backend.Fx.NLogLogging/NLogLogger.cs index 7f4fb1e8..103b0698 100644 --- a/src/implementations/Backend.Fx.NLogLogging/NLogLogger.cs +++ b/src/implementations/logging/Backend.Fx.NLogLogging/NLogLogger.cs @@ -3,14 +3,17 @@ using System.Globalization; using Backend.Fx.Logging; using NLog; +using ILogger = Backend.Fx.Logging.ILogger; + // ReSharper disable TemplateIsNotCompileTimeConstantProblem namespace Backend.Fx.NLogLogging { - using BackendFxILogger = Logging.ILogger; + using BackendFxILogger = ILogger; using NLogILogger = NLog.ILogger; using NLogLogLevel = LogLevel; + [DebuggerStepThrough] public class NLogLogger : BackendFxILogger { @@ -185,17 +188,23 @@ public IDisposable TraceDuration(string beginMessage, string endMessage) [MessageTemplateFormatMethod("format")] public void Trace(string format, params object[] args) { - if (IsTraceEnabled()) _nlogLogger.Trace(CultureInfo.InvariantCulture, format, args); + if (IsTraceEnabled()) + { + _nlogLogger.Trace(CultureInfo.InvariantCulture, format, args); + } } [MessageTemplateFormatMethod("format")] public Exception Trace(Exception exception, string format, params object[] args) { - if (IsTraceEnabled()) _nlogLogger.Trace(exception, CultureInfo.InvariantCulture, format, args); + if (IsTraceEnabled()) + { + _nlogLogger.Trace(exception, CultureInfo.InvariantCulture, format, args); + } return exception; } #endregion } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.NLogLogging/NLogLoggerFactory.cs b/src/implementations/logging/Backend.Fx.NLogLogging/NLogLoggerFactory.cs similarity index 96% rename from src/implementations/Backend.Fx.NLogLogging/NLogLoggerFactory.cs rename to src/implementations/logging/Backend.Fx.NLogLogging/NLogLoggerFactory.cs index 911c289a..c5316b0c 100644 --- a/src/implementations/Backend.Fx.NLogLogging/NLogLoggerFactory.cs +++ b/src/implementations/logging/Backend.Fx.NLogLogging/NLogLoggerFactory.cs @@ -24,7 +24,7 @@ public ILogger Create(string s) public ILogger Create(Type t) { string s = t.FullName; - var indexOf = s?.IndexOf('[') ?? 0; + int indexOf = s?.IndexOf('[') ?? 0; if (indexOf > 0) { s = s?.Substring(0, indexOf); @@ -54,4 +54,4 @@ public static void Configure(string nlogConfigPath) LogManager.Configuration = new XmlLoggingConfiguration(nlogConfigPath); } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.NLogLogging/Properties/AssemblyInfo.cs b/src/implementations/logging/Backend.Fx.NLogLogging/Properties/AssemblyInfo.cs similarity index 74% rename from src/implementations/Backend.Fx.NLogLogging/Properties/AssemblyInfo.cs rename to src/implementations/logging/Backend.Fx.NLogLogging/Properties/AssemblyInfo.cs index 5f565d7d..43603728 100644 --- a/src/implementations/Backend.Fx.NLogLogging/Properties/AssemblyInfo.cs +++ b/src/implementations/logging/Backend.Fx.NLogLogging/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyVersion("0.0.0.0")] [assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/implementations/Backend.Fx.SerilogLogging/Backend.Fx.SerilogLogging.csproj b/src/implementations/logging/Backend.Fx.SerilogLogging/Backend.Fx.SerilogLogging.csproj similarity index 79% rename from src/implementations/Backend.Fx.SerilogLogging/Backend.Fx.SerilogLogging.csproj rename to src/implementations/logging/Backend.Fx.SerilogLogging/Backend.Fx.SerilogLogging.csproj index 8e2a2d53..ce08f1b4 100644 --- a/src/implementations/Backend.Fx.SerilogLogging/Backend.Fx.SerilogLogging.csproj +++ b/src/implementations/logging/Backend.Fx.SerilogLogging/Backend.Fx.SerilogLogging.csproj @@ -5,12 +5,12 @@ - + + - - + diff --git a/src/implementations/Backend.Fx.SerilogLogging/SerilogLogger.cs b/src/implementations/logging/Backend.Fx.SerilogLogging/SerilogLogger.cs similarity index 84% rename from src/implementations/Backend.Fx.SerilogLogging/SerilogLogger.cs rename to src/implementations/logging/Backend.Fx.SerilogLogging/SerilogLogger.cs index 0dfec444..826b3a18 100644 --- a/src/implementations/Backend.Fx.SerilogLogging/SerilogLogger.cs +++ b/src/implementations/logging/Backend.Fx.SerilogLogging/SerilogLogger.cs @@ -1,22 +1,23 @@ using System; using System.Diagnostics; -using Serilog; +using Backend.Fx.Logging; using Serilog.Core; using Serilog.Events; + // ReSharper disable TemplateIsNotCompileTimeConstantProblem namespace Backend.Fx.SerilogLogging { [DebuggerStepThrough] - public class SerilogLogger : Logging.ILogger + public class SerilogLogger : ILogger { - private readonly ILogger _logger; + private readonly Serilog.ILogger _logger; - internal SerilogLogger(ILogger logger) + internal SerilogLogger(Serilog.ILogger logger) { _logger = logger; } - + #region fatal public Exception Fatal(Exception exception) @@ -96,12 +97,12 @@ public Exception Info(Exception exception) public IDisposable InfoDuration(string activity) { - return new Logging.DurationLogger(s => Info(s), activity); + return new DurationLogger(s => Info(s), activity); } public IDisposable InfoDuration(string beginMessage, string endMessage) { - return new Logging.DurationLogger(s => Info(s), beginMessage, endMessage); + return new DurationLogger(s => Info(s), beginMessage, endMessage); } [MessageTemplateFormatMethod("format")] @@ -134,12 +135,12 @@ public Exception Debug(Exception exception) public IDisposable DebugDuration(string activity) { - return new Logging.DurationLogger(s => Debug(s), activity); + return new DurationLogger(s => Debug(s), activity); } public IDisposable DebugDuration(string beginMessage, string endMessage) { - return new Logging.DurationLogger(s => Debug(s), beginMessage, endMessage); + return new DurationLogger(s => Debug(s), beginMessage, endMessage); } [MessageTemplateFormatMethod("format")] @@ -172,28 +173,34 @@ public Exception Trace(Exception exception) public IDisposable TraceDuration(string activity) { - return new Logging.DurationLogger(s => Trace(s), activity); + return new DurationLogger(s => Trace(s), activity); } public IDisposable TraceDuration(string beginMessage, string endMessage) { - return new Logging.DurationLogger(s => Trace(s), beginMessage, endMessage); + return new DurationLogger(s => Trace(s), beginMessage, endMessage); } [MessageTemplateFormatMethod("format")] public void Trace(string format, params object[] args) { - if (IsTraceEnabled()) _logger.Verbose(format, args); + if (IsTraceEnabled()) + { + _logger.Verbose(format, args); + } } [MessageTemplateFormatMethod("format")] public Exception Trace(Exception exception, string format, params object[] args) { - if (IsTraceEnabled()) _logger.Verbose(exception, format, args); + if (IsTraceEnabled()) + { + _logger.Verbose(exception, format, args); + } return exception; } #endregion } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.SerilogLogging/SerilogLoggerFactory.cs b/src/implementations/logging/Backend.Fx.SerilogLogging/SerilogLoggerFactory.cs similarity index 94% rename from src/implementations/Backend.Fx.SerilogLogging/SerilogLoggerFactory.cs rename to src/implementations/logging/Backend.Fx.SerilogLogging/SerilogLoggerFactory.cs index d2938de4..31406c0e 100644 --- a/src/implementations/Backend.Fx.SerilogLogging/SerilogLoggerFactory.cs +++ b/src/implementations/logging/Backend.Fx.SerilogLogging/SerilogLoggerFactory.cs @@ -20,7 +20,7 @@ public SerilogLoggerFactory(Logger logger = null) public ILogger Create(string s) { - return TryGetContextTypeFromString(s, out Type t) + return TryGetContextTypeFromString(s, out var t) ? new SerilogLogger(_rootLogger.ForContext(t)) : new SerilogLogger(_rootLogger); } @@ -44,7 +44,7 @@ public void Shutdown() { Log.CloseAndFlush(); } - + private static bool TryGetContextTypeFromString(string s, out Type type) { try @@ -55,8 +55,8 @@ private static bool TryGetContextTypeFromString(string s, out Type type) { type = null; } - + return type != null; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj similarity index 73% rename from tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj index 6bdd9f62..b95d89ec 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj @@ -1,37 +1,38 @@  - netcoreapp3.1 + net5.0 + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + - - - + - + + diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs similarity index 87% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs index 27c8be41..5b8f9abf 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs @@ -9,8 +9,7 @@ public class Blog : AggregateRoot { [UsedImplicitly] private Blog() - { - } + { } public Blog(int id, string name) : base(id) { @@ -31,7 +30,10 @@ public Post AddPost(IEntityIdGenerator idGenerator, string name, bool isPublic = public void Modify(string modified) { Name = modified; - foreach (Post post in Posts) post.SetName(modified); + foreach (var post in Posts) + { + post.SetName(modified); + } } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs similarity index 94% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs index 958e27a8..f89f183a 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs @@ -5,6 +5,5 @@ namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain { [UsedImplicitly] public class BlogAuthorization : AllowAll - { - } -} \ No newline at end of file + { } +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs similarity index 96% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs index a21db5ab..6a0f5a8a 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs @@ -7,8 +7,7 @@ public class Blogger : AggregateRoot { [UsedImplicitly] private Blogger() - { - } + { } public Blogger(int id, string lastName, string firstName) : base(id) { @@ -17,7 +16,9 @@ public Blogger(int id, string lastName, string firstName) : base(id) } public string LastName { get; set; } + public string FirstName { get; set; } + public string Bio { get; set; } } -} \ No newline at end of file +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs new file mode 100644 index 00000000..f6e63240 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs @@ -0,0 +1,7 @@ +using Backend.Fx.Patterns.Authorization; + +namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +{ + public class BloggerAuthorization : AllowAll + { } +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs similarity index 67% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs index e71875c0..8c94cd98 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs @@ -8,24 +8,27 @@ public class Post : Entity { [UsedImplicitly] private Post() - { - } + { } public Post(int id, Blog blog, string name, bool isPublic = false) : base(id) { Blog = blog; BlogId = blog.Id; Name = name; - TargetAudience = new TargetAudience {IsPublic = isPublic, Culture = "fr-FR"}; + TargetAudience = new TargetAudience { IsPublic = isPublic, Culture = "fr-FR" }; } - [UsedImplicitly] public int BlogId { get; private set; } + [UsedImplicitly] + public int BlogId { get; private set; } - [UsedImplicitly] public Blog Blog { get; private set; } + [UsedImplicitly] + public Blog Blog { get; private set; } - [UsedImplicitly] public string Name { get; private set; } + [UsedImplicitly] + public string Name { get; private set; } - [UsedImplicitly] public TargetAudience TargetAudience { get; set; } + [UsedImplicitly] + public TargetAudience TargetAudience { get; set; } public void SetName(string name) { @@ -33,6 +36,7 @@ public void SetName(string name) } } + public class TargetAudience : ValueObject { public string Culture { get; set; } @@ -45,4 +49,4 @@ protected override IEnumerable GetEqualityComponents() yield return IsPublic; } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs similarity index 99% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs index 4f21dddb..252a708f 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs @@ -24,4 +24,4 @@ public override void ApplyEfMapping(ModelBuilder modelBuilder) modelBuilder.Entity().OwnsOne(p => p.TargetAudience); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs similarity index 93% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs index 2659bcd9..aaee3d9e 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs @@ -3,6 +3,5 @@ namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence { public class BloggerMapping : PlainAggregateMapping - { - } -} \ No newline at end of file + { } +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs similarity index 99% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs index bd40f2f5..25bf9e15 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs @@ -15,7 +15,9 @@ public TestDbContext([NotNull] DbContextOptions options) : base(o public DbSet Bloggers { get; [UsedImplicitly] set; } public DbSet Blogs { get; [UsedImplicitly] set; } + public DbSet Posts { get; [UsedImplicitly] set; } + public DbSet Tenants { get; [UsedImplicitly] set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -25,4 +27,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.RegisterEntityIdAsNeverGenerated(); } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs similarity index 75% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs index cefd3713..dec7bdfd 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs @@ -11,7 +11,8 @@ public class TestDbContextFactory : IDesignTimeDbContextFactory { public TestDbContext CreateDbContext(string[] args) { - return new TestDbContext(new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options); + return new TestDbContext( + new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options); } } -} \ No newline at end of file +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestScopedServices.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestScopedServices.cs new file mode 100644 index 00000000..22374615 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestScopedServices.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.EventAggregation.Integration; +using FakeItEasy; + +namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +{ + public class TestScopedServices : EfCoreScopedServices + { + public TestScopedServices( + TestDbContext dbContext, + IClock clock, + IIdentity identity, + TenantId tenantId, + params Assembly[] assemblies) + : base(dbContext, clock, identity, tenantId, assemblies) + { } + + public override IDomainEventAggregator EventAggregator { get; } = A.Fake(); + + public override IMessageBusScope MessageBusScope { get; } = A.Fake(); + + protected override IPersistenceSession CreatePersistenceSession() + { + return new EfCorePersistenceSession(DbContext, IdentityHolder, Clock); + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/EntityIdGenerator.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/EntityIdGenerator.cs new file mode 100644 index 00000000..4fdd3ed6 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/EntityIdGenerator.cs @@ -0,0 +1,14 @@ +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.MsSql +{ + public class EntityIdGenerator : SequenceHiLoIdGenerator, IEntityIdGenerator + { + public EntityIdGenerator(IDbConnectionFactory dbConnectionFactory) + : base(new EntityIdSequence(dbConnectionFactory)) + { } + + protected override int BlockSize => 1000; + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/EntityIdSequence.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/EntityIdSequence.cs new file mode 100644 index 00000000..88d78214 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/EntityIdSequence.cs @@ -0,0 +1,14 @@ +using Backend.Fx.Environment.Persistence; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.MsSql +{ + public class EntityIdSequence : MsSqlSequence + { + public EntityIdSequence(IDbConnectionFactory dbConnectionFactory) : base(dbConnectionFactory) + { } + + public override int Increment { get; } = 1000; + + protected override string SequenceName { get; } = "SEQ_EntityId"; + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlEfCoreSingletonServices.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlEfCoreSingletonServices.cs new file mode 100644 index 00000000..0bb8fa30 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlEfCoreSingletonServices.cs @@ -0,0 +1,45 @@ +using System.Data; +using System.Data.SqlClient; +using System.Security.Principal; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Logging; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.MsSql +{ + public class MsSqlEfCoreSingletonServices : EfCoreSingletonServices + { + public MsSqlEfCoreSingletonServices(string connectionString) : base( + connectionString, + new DbContextOptionsBuilder().UseSqlServer(connectionString) + .UseLoggerFactory(new FrameworkToBackendFxLoggerFactory()) + .Options, + new EntityIdGenerator(new MsSqlConnectionFactory(connectionString)), + typeof(Blog).Assembly) + { } + + public override TestScopedServices BeginScope(TenantId tenantId, IIdentity identity = null) + { + return new TestScopedServices(new TestDbContext(DbContextOptions), Clock, identity, tenantId, Assemblies); + } + + + private class MsSqlConnectionFactory : IDbConnectionFactory + { + private readonly string _connectionString; + + public MsSqlConnectionFactory(string connectionString) + { + _connectionString = connectionString; + } + + public IDbConnection Create() + { + return new SqlConnection(_connectionString); + } + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlServerUtil.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlServerUtil.cs new file mode 100644 index 00000000..8166e64d --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlServerUtil.cs @@ -0,0 +1,149 @@ +using System; +using Backend.Fx.Logging; +using Microsoft.Data.SqlClient; +using Polly; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.MsSql +{ + public class MsSqlServerUtil + { + private static readonly ILogger Logger = LogManager.Create(); + + public MsSqlServerUtil(string connectionString) + { + ConnectionString = connectionString; + } + + public string ConnectionString { get; } + + public static MsSqlServerUtil DetectSqlServer(string connectionStringEnvironmentVariable = null) + { + // the build agent is in charge to start a sql server (e.g. using docker) and to provide the connection string + // via environment variable. If the environment variable is set, we're doing some retries allowing the sql server + // to get up and running + + // other generally known local sql instances are either up or considered as non existent + string environmentVariableValue = connectionStringEnvironmentVariable == null + ? null + : System.Environment.GetEnvironmentVariable(connectionStringEnvironmentVariable); + + string connectionString = DetectLocalSqlServerWithRetries(environmentVariableValue, 7) + ?? DetectLocalSqlServer("Server=.\\SQLExpress;Integrated Security=true;") + ?? DetectLocalSqlServer("Server=.;Integrated Security=true;") + ?? DetectLocalSqlServer( + "Server=localhost;User=sa;Password=yourStrong(!)Password"); // default from docker hub + + if (connectionString != null) + { + Logger.Info("Detected MSSQL Connection string for test execution: " + connectionString); + return new MsSqlServerUtil(connectionString); + } + + throw new InvalidOperationException("Cannot run tests because no SQL database was detected"); + } + + private static string DetectLocalSqlServerWithRetries(string connectionString, int retries) + { + if (!string.IsNullOrEmpty(connectionString)) + { + Logger.Info( + $"Probing for SQL instance using connection string {connectionString} with {retries} retries."); + return Policy + .HandleResult(r => r == null) + .WaitAndRetry( + retries, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(retryAttempt, 2)) /* 1,2,4,8,16,32,64,128 secs */) + .Execute(() => DetectLocalSqlServer(connectionString)); + } + + return null; + } + + private static string DetectLocalSqlServer(string connectionString) + { + if (!string.IsNullOrEmpty(connectionString)) + { + try + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + var command = connection.CreateCommand(); + command.CommandText = "SELECT 1"; + command.ExecuteScalar(); + } + + return connectionString; + } + catch (Exception ex) + { + Logger.Info(ex, $"No MSSQL instance was found using connection string [{connectionString}]"); + } + } + + return null; + } + + public void EnsureExistingDatabase() + { + var sb = new SqlConnectionStringBuilder(ConnectionString); + string dbName = sb.InitialCatalog; + sb.InitialCatalog = "master"; + using (var connection = new SqlConnection(sb.ConnectionString)) + { + connection.Open(); + bool isExistent; + using (var command = connection.CreateCommand()) + { + command.CommandText = $"SELECT count(*) FROM sys.databases WHERE Name = '{dbName}'"; + isExistent = (int)command.ExecuteScalar() == 1; + } + + if (!isExistent) + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "CREATE DATABASE [" + dbName + "]"; + command.ExecuteNonQuery(); + } + } + } + } + + public void EnsureDroppedDatabase(string dbName) + { + using (var connection = new SqlConnection(ConnectionString)) + { + connection.Open(); + + using (var dropCommand = connection.CreateCommand()) + { + dropCommand.CommandText = + $"IF EXISTS(SELECT * FROM sys.Databases WHERE Name='{dbName}') ALTER DATABASE [{dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"; + dropCommand.ExecuteNonQuery(); + } + + using (var dropCommand = connection.CreateCommand()) + { + dropCommand.CommandText + = $"IF EXISTS(SELECT * FROM sys.Databases WHERE Name='{dbName}') DROP DATABASE [{dbName}]"; + dropCommand.ExecuteNonQuery(); + } + } + } + + public void CreateDatabase(string dbName) + { + using (var connection = new SqlConnection(ConnectionString)) + { + connection.Open(); + + using (var createCommand = connection.CreateCommand()) + { + createCommand.CommandText = $"CREATE DATABASE [{dbName}]"; + createCommand.ExecuteNonQuery(); + } + } + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlTestDb.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlTestDb.cs new file mode 100644 index 00000000..e2b71c66 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/MsSql/MsSqlTestDb.cs @@ -0,0 +1,23 @@ +using Microsoft.Data.SqlClient; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.MsSql +{ + public class MsSqlTestDb + { + public MsSqlTestDb(string testDbName) + { + var sqlUtil = MsSqlServerUtil.DetectSqlServer("BACKENDFXTESTDB"); + sqlUtil.EnsureDroppedDatabase(testDbName); + sqlUtil.CreateDatabase(testDbName); + + var builder = new SqlConnectionStringBuilder(sqlUtil.ConnectionString) + { + InitialCatalog = testDbName, + MultipleActiveResultSets = true + }; + ConnectionString = builder.ConnectionString; + } + + public string ConnectionString { get; } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/PersistenceFixture.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/PersistenceFixture.cs new file mode 100644 index 00000000..c1a97cf8 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/PersistenceFixture.cs @@ -0,0 +1,48 @@ +using System.Security.Principal; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCorePersistence.Tests.Fixtures.MsSql; +using Backend.Fx.EfCorePersistence.Tests.Fixtures.Sqlite; +using Backend.Fx.Environment.MultiTenancy; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +{ + public class PersistenceFixture + { + public static bool RunTestsWithSqlServerDatabase = false; + + public PersistenceFixture() + { + if (RunTestsWithSqlServerDatabase) + { + var testDb = new MsSqlTestDb("BackendFxEfCorePersistenceTests"); + SingletonServices = new MsSqlEfCoreSingletonServices(testDb.ConnectionString); + } + else + { + SingletonServices = new SqliteEfCoreSingletonServices(); + } + + using (var dbContext = UseDbContext()) + { + dbContext.Database.EnsureCreated(); + } + } + + public EfCoreSingletonServices SingletonServices { get; } + + public TenantId TenantId { get; } = new(999); + + public string ConnectionString => SingletonServices.ConnectionString; + + public TestScopedServices BeginScope(IIdentity identity = null) + { + return SingletonServices.BeginScope(TenantId, identity); + } + + public DbContext UseDbContext() + { + return new TestDbContext(SingletonServices.DbContextOptions); + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/EntityIdGenerator.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/EntityIdGenerator.cs new file mode 100644 index 00000000..382c0c52 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/EntityIdGenerator.cs @@ -0,0 +1,13 @@ +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.Sqlite +{ + public class EntityIdGenerator : SequenceHiLoIdGenerator, IEntityIdGenerator + { + public EntityIdGenerator() + : base(new FakeSequence()) + { } + + protected override int BlockSize => 1000; + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/FakeSequence.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/FakeSequence.cs new file mode 100644 index 00000000..9e94befd --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/FakeSequence.cs @@ -0,0 +1,20 @@ +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.Sqlite +{ + public class FakeSequence : ISequence + { + private static int _currentValue = 1; + + public void EnsureSequence() + { } + + public int GetNextValue() + { + _currentValue += Increment; + return _currentValue; + } + + public int Increment => 1000; + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/SqliteEfCoreSingletonServices.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/SqliteEfCoreSingletonServices.cs new file mode 100644 index 00000000..ecd23ef6 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Fixtures/Sqlite/SqliteEfCoreSingletonServices.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Security.Principal; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Logging; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCorePersistence.Tests.Fixtures.Sqlite +{ + public class SqliteEfCoreSingletonServices : EfCoreSingletonServices + { + public SqliteEfCoreSingletonServices() : this("Data Source=" + Path.GetTempFileName()) + { } + + public SqliteEfCoreSingletonServices(string connectionString) : base( + connectionString, + new DbContextOptionsBuilder().UseSqlite(connectionString) + .UseLoggerFactory(new FrameworkToBackendFxLoggerFactory()) + .Options, + new EntityIdGenerator(), + typeof(Blog).Assembly) + { } + + public override TestScopedServices BeginScope(TenantId tenantId, IIdentity identity = null) + { + return new TestScopedServices(new TestDbContext(DbContextOptions), Clock, identity, tenantId, Assemblies); + } + } +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs similarity index 100% rename from tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.cs similarity index 100% rename from tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.cs diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs similarity index 100% rename from tests/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs similarity index 67% rename from tests/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs index 6fde5b9a..3192e6bd 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs @@ -3,7 +3,10 @@ using MarcWittke.Xunit.AssemblyFixture; using Xunit; -[assembly: TestFramework("MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", "MarcWittke.Xunit.AssemblyFixture")] +[assembly: + TestFramework( + "MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", + "MarcWittke.Xunit.AssemblyFixture")] [assembly: AssemblyFixture(typeof(TestLoggingFixture))] namespace Backend.Fx.EfCorePersistence.Tests @@ -11,7 +14,6 @@ namespace Backend.Fx.EfCorePersistence.Tests public class TestLoggingFixture : LoggingFixture { public TestLoggingFixture() : base("Backend.Fx") - { - } + { } } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs similarity index 52% rename from tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs index 07fdd6b1..5b510bb5 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs @@ -4,37 +4,38 @@ using Microsoft.EntityFrameworkCore; using Xunit; +// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local + namespace Backend.Fx.EfCorePersistence.Tests { - public class TheDbContext + public class TheDbContext : IClassFixture { - public TheDbContext() - { - _fixture = new SqliteDatabaseFixture(); - _fixture.CreateDatabase(); - } - - private readonly DatabaseFixture _fixture; private static int _nextTenantId = 2675; + private readonly PersistenceFixture _fixture; private readonly int _tenantId = _nextTenantId++; + public TheDbContext(PersistenceFixture fixture) + { + _fixture = fixture; + } + [Fact] - public void CanClearAndReplaceDependentEntites() + public void CanClearAndReplaceDependentEntities() { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) + using (var scope = _fixture.BeginScope()) { - var blog = new Blog(1, "original blog") {TenantId = _tenantId}; + var blog = new Blog(1, "original blog") { TenantId = _tenantId }; blog.Posts.Add(new Post(1, blog, "new name 1")); blog.Posts.Add(new Post(2, blog, "new name 2")); blog.Posts.Add(new Post(3, blog, "new name 3")); blog.Posts.Add(new Post(4, blog, "new name 4")); blog.Posts.Add(new Post(5, blog, "new name 5")); - dbSession.DbContext.Add(blog); + scope.DbContext.Add(blog); } - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) + using (var scope = _fixture.BeginScope()) { - Blog blog = dbSession.DbContext.Blogs.Include(b => b.Posts).Single(b => b.Id == 1); + var blog = scope.DbContext.Blogs.Include(b => b.Posts).Single(b => b.Id == 1); blog.Posts.Clear(); blog.Posts.Add(new Post(6, blog, "new name 6")); blog.Posts.Add(new Post(7, blog, "new name 7")); @@ -43,16 +44,22 @@ public void CanClearAndReplaceDependentEntites() blog.Posts.Add(new Post(10, blog, "new name 10")); } - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) + using (var scope = _fixture.BeginScope()) { - Blog blog = dbSession.DbContext.Blogs.Include(b => b.Posts).Single(b => b.Id == 1); + var blog = scope.DbContext.Blogs.Include(b => b.Posts).Single(b => b.Id == 1); Assert.Equal(5, blog.Posts.Count); - for (var i = 1; i <= 5; i++) Assert.DoesNotContain(blog.Posts, p => p.Id == i); + for (var i = 1; i <= 5; i++) + { + Assert.DoesNotContain(blog.Posts, p => p.Id == i); + } - for (var i = 6; i <= 10; i++) Assert.Contains(blog.Posts, p => p.Id == i); + for (var i = 6; i <= 10; i++) + { + Assert.Contains(blog.Posts, p => p.Id == i); + } } } } -} \ No newline at end of file +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs new file mode 100644 index 00000000..88060220 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.IdGeneration; +using Dapper; +using Xunit; + +namespace Backend.Fx.EfCorePersistence.Tests +{ + public class TheRepositoryOfComposedAggregate + { + private readonly PersistenceFixture _fixture; + + private readonly IEntityIdGenerator _idGenerator; + + private readonly IEqualityComparer _tolerantDateTimeComparer = + new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000)); + + public TheRepositoryOfComposedAggregate() + { + _fixture = new PersistenceFixture(); + _idGenerator = _fixture.SingletonServices.EntityIdGenerator; + } + + private int CreateBlogWithPost(TestScopedServices scope, int postCount = 1) + { + int blogId = _idGenerator.NextId(); + scope.DbConnection.Execute( + "INSERT INTO Blogs (Id, TenantId, Name, CreatedOn, CreatedBy) " + + $"VALUES ({blogId}, {scope.TenantId.Value}, 'my blog', CURRENT_TIMESTAMP, 'persistence test')"); + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); + Assert.Equal(1, count); + + for (var i = 0; i < postCount; i++) + { + scope.DbConnection.Execute( + "INSERT INTO Posts (Id, BlogId, Name, TargetAudience_IsPublic, TargetAudience_Culture, CreatedOn, CreatedBy) " + + $"VALUES ({_idGenerator.NextId()}, {blogId}, 'my post {i:00}', '1', 'de-DE', CURRENT_TIMESTAMP, 'persistence test')"); + } + + return blogId; + } + + // //FAILING!!!! + // // this shows, that ValueObjects treated as OwnedTypes are not supported very well + // [Fact] + // public void CanUpdateDependantValueObject() + // { + // int id; + // using (var scope = _fixture.BeginScope()) + // { + // id = CreateBlogWithPost(scope, 10); + // } + // + // Post post; + // DateTime? expectedChangedOn; + // using (var scope = _fixture.BeginScope()) + // { + // expectedChangedOn = _fixture.SingletonServices.Clock.UtcNow; + // post = scope.GetRepository().Single(id).Posts.First(); + // post.TargetAudience = new TargetAudience { Culture = "es-AR", IsPublic = false }; + // } + // + // using (var scope = _fixture.BeginScope()) + // { + // string culture = scope.DbConnection.ExecuteScalar($"SELECT TargetAudience_Culture FROM Posts where id = {post.Id}"); + // Assert.Equal("es-AR", culture); + // + // var blog = scope.GetRepository().Single(id); + // + // string strChangedOn = scope.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); + // DateTime changedOn = DateTime.Parse(strChangedOn); + // Assert.Equal(expectedChangedOn, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); + // } + // } + + [Fact] + public void CanAddDependent() + { + using (var scope = _fixture.BeginScope()) + { + int id = CreateBlogWithPost(scope, 10); + + IRepository sut = scope.GetRepository(); + + var blog = sut.Single(id); + blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "added")); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(11, count); + } + } + + [Fact] + public void CanCreate() + { + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); + Assert.Equal(0, count); + + count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(0, count); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + var blog = new Blog(_idGenerator.NextId(), "my blog"); + blog.AddPost(_idGenerator, "my post"); + sut.Add(blog); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); + Assert.Equal(1, count); + + count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(1, count); + } + } + + [Fact] + public void CanDelete() + { + using (var scope = _fixture.BeginScope()) + { + int id = CreateBlogWithPost(scope); + + IRepository sut = scope.GetRepository(); + var blog = sut.Single(id); + sut.Delete(blog); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); + Assert.Equal(0, count); + + count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(0, count); + } + } + + [Fact] + public void CanDeleteDependent() + { + int id; + using (var scope = _fixture.BeginScope()) + { + id = CreateBlogWithPost(scope, 10); + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(10, count); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + var blog = sut.Single(id); + var firstPost = blog.Posts.First(); + firstPost.SetName("Something different"); + blog.Posts.Remove(firstPost); + + scope.DbContext.TraceChangeTrackerState(); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(9, count); + } + } + + [Fact] + public void CanRead() + { + int id; + Blog blog; + + using (var scope = _fixture.BeginScope()) + { + id = CreateBlogWithPost(scope); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + blog = sut.Single(id); + } + + Assert.NotNull(blog); + Assert.Equal(id, blog.Id); + Assert.Equal("my blog", blog.Name); + Assert.NotEmpty(blog.Posts); + } + + [Fact] + public void CanReplaceDependentCollection() + { + int id; + using (var scope = _fixture.BeginScope()) + { + id = CreateBlogWithPost(scope, 10); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + var blog = sut.Single(id); + blog.Posts.Clear(); + blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 1")); + blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 2")); + blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 3")); + blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 4")); + blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 5")); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); + Assert.Equal(5, count); + } + } + + [Fact] + public void CanUpdate() + { + int id; + using (var scope = _fixture.BeginScope()) + { + id = CreateBlogWithPost(scope); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + var blog = sut.Single(id); + blog.Modify("modified"); + } + + using (var scope = _fixture.BeginScope()) + { + Assert.Equal(1, scope.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs")); + Assert.Equal(id, scope.DbConnection.ExecuteScalar("SELECT Id FROM Blogs LIMIT 1")); + Assert.Equal("modified", scope.DbConnection.ExecuteScalar("SELECT Name FROM Blogs LIMIT 1")); + Assert.Equal("modified", scope.DbConnection.ExecuteScalar("SELECT Name FROM Posts LIMIT 1")); + } + } + + [Fact] + public void CanUpdateDependant() + { + _fixture.SingletonServices.Clock.OverrideUtcNow(new DateTime(2000, 1, 2, 11, 22, 33)); + int id; + using (var scope = _fixture.BeginScope()) + { + id = CreateBlogWithPost(scope, 10); + } + + Post post; + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + var blog = sut.Single(id); + post = blog.Posts.First(); + post.SetName("modified"); + } + + using (var scope = _fixture.BeginScope()) + { + var name = scope.DbConnection.ExecuteScalar($"SELECT name FROM Posts where id = {post.Id}"); + Assert.Equal("modified", name); + + var strChangedOn + = scope.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); + var changedOn = DateTime.Parse(strChangedOn); + Assert.Equal( + _fixture.SingletonServices.Clock.UtcNow, + changedOn, + new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); + } + } + + [Fact] + public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() + { + _fixture.SingletonServices.Clock.OverrideUtcNow(new DateTime(2000, 1, 2, 11, 22, 33)); + + int id; + using (var scope = _fixture.BeginScope()) + { + id = CreateBlogWithPost(scope, 10); + } + + var expectedModifiedOn = _fixture.SingletonServices.Clock.Advance(TimeSpan.FromHours(1)); + + using (var scope = _fixture.BeginScope()) + { + IRepository sut = scope.GetRepository(); + var b = sut.Single(id); + b.Posts.Remove(b.Posts.First()); + } + + using (var scope = _fixture.BeginScope()) + { + var blog = scope.DbContext.Set().Find(id); + Assert.NotNull(blog.ChangedOn); + Assert.Equal(expectedModifiedOn, blog.ChangedOn.Value, _tolerantDateTimeComparer); + } + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs new file mode 100644 index 00000000..c2965224 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -0,0 +1,180 @@ +using System; +using System.Threading.Tasks; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.Extensions; +using Dapper; +using Xunit; + +namespace Backend.Fx.EfCorePersistence.Tests +{ + public class TheRepositoryOfPlainAggregate : IClassFixture + { + private readonly PersistenceFixture _fixture; + + public TheRepositoryOfPlainAggregate(PersistenceFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void CanCreate() + { + using (var scope = _fixture.BeginScope()) + { + IRepository repo = scope.GetRepository(); + repo.Add(new Blogger(345, "Metulsky", "Bratislav")); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT Count(*) FROM Bloggers"); + Assert.Equal(1, count); + + count = scope.DbConnection.ExecuteScalar("SELECT Count(*) FROM Bloggers WHERE Id=345"); + Assert.Equal(1, count); + } + } + + [Fact] + public void CanDelete() + { + using (var scope = _fixture.BeginScope()) + { + scope.DbConnection.Execute( + "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + + $"VALUES (555, {scope.TenantId.Value}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository repo = scope.GetRepository(); + var bratislavMetulsky = repo.Single(555); + repo.Delete(bratislavMetulsky); + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar("SELECT Count(*) FROM Bloggers WHERE Id = 555"); + Assert.Equal(0, count); + } + } + + [Fact] + public void CanRead() + { + using (var scope = _fixture.BeginScope()) + { + scope.DbConnection.Execute( + "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + + $"VALUES (444, {scope.TenantId.Value}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); + + { + IRepository repo = scope.GetRepository(); + + bool any = repo.Any(); + Assert.True(any); + + Blogger[] all = repo.GetAll(); + Assert.NotEmpty(all); + + var bratislavMetulsky = repo.Single(444); + Assert.Equal(scope.TenantId.Value, bratislavMetulsky.TenantId); + Assert.Equal("the test", bratislavMetulsky.CreatedBy); + Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal("Bratislav", bratislavMetulsky.FirstName); + Assert.Equal("Metulsky", bratislavMetulsky.LastName); + Assert.Equal("whatever", bratislavMetulsky.Bio); + + bratislavMetulsky = repo.SingleOrDefault(444); + Assert.NotNull(bratislavMetulsky); + Assert.Equal(scope.TenantId.Value, bratislavMetulsky.TenantId); + Assert.Equal("the test", bratislavMetulsky.CreatedBy); + Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal("Bratislav", bratislavMetulsky.FirstName); + Assert.Equal("Metulsky", bratislavMetulsky.LastName); + Assert.Equal("whatever", bratislavMetulsky.Bio); + } + } + } + + [Fact] + public async Task CanReadAsync() + { + using (var scope = _fixture.BeginScope()) + { + await scope.DbConnection.ExecuteAsync( + "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + + $"VALUES (555, {scope.TenantId.Value}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); + + { + IAsyncRepository repo = scope.GetAsyncRepository(); + + bool any = await repo.AnyAsync(); + Assert.True(any); + + Blogger[] all = await repo.GetAllAsync(); + Assert.NotEmpty(all); + + var bratislavMetulsky = await repo.SingleAsync(555); + Assert.Equal(scope.TenantId.Value, bratislavMetulsky.TenantId); + Assert.Equal("the test", bratislavMetulsky.CreatedBy); + Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal("Bratislav", bratislavMetulsky.FirstName); + Assert.Equal("Metulsky", bratislavMetulsky.LastName); + Assert.Equal("whatever", bratislavMetulsky.Bio); + + bratislavMetulsky = await repo.SingleOrDefaultAsync(555); + Assert.NotNull(bratislavMetulsky); + Assert.Equal(scope.TenantId.Value, bratislavMetulsky.TenantId); + Assert.Equal("the test", bratislavMetulsky.CreatedBy); + Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal("Bratislav", bratislavMetulsky.FirstName); + Assert.Equal("Metulsky", bratislavMetulsky.LastName); + Assert.Equal("whatever", bratislavMetulsky.Bio); + } + } + } + + [Fact] + public void CanUpdate() + { + using (var scope = _fixture.BeginScope()) + { + scope.DbConnection.Execute( + "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + + $"VALUES (456, {scope.TenantId.Value}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository repo = scope.GetRepository(); + var bratislavMetulsky = repo.Single(456); + bratislavMetulsky.FirstName = "Johnny"; + bratislavMetulsky.LastName = "Flash"; + bratislavMetulsky.Bio = "Der lustige Clown"; + } + + using (var scope = _fixture.BeginScope()) + { + var count = scope.DbConnection.ExecuteScalar( + $"SELECT Count(*) FROM Bloggers WHERE FirstName = 'Johnny' AND LastName = 'Flash' AND TenantId = '{scope.TenantId.Value}'"); + Assert.Equal(1, count); + } + + using (var scope = _fixture.BeginScope()) + { + IRepository repo = scope.GetRepository(); + var johnnyFlash = repo.Single(456); + Assert.Equal( + DateTime.UtcNow, + johnnyFlash.ChangedOn, + new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000))); + Assert.Equal(scope.IdentityHolder.Current.Name, johnnyFlash.ChangedBy); + Assert.Equal("Johnny", johnnyFlash.FirstName); + Assert.Equal("Flash", johnnyFlash.LastName); + } + } + } +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/AggregateMapping.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/AggregateMapping.cs similarity index 99% rename from src/implementations/Backend.Fx.EfCorePersistence/AggregateMapping.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/AggregateMapping.cs index a7ce812b..6239652a 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/AggregateMapping.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/AggregateMapping.cs @@ -12,4 +12,4 @@ public abstract class AggregateMapping : IAggregateMapping where T : Aggre public abstract void ApplyEfMapping(ModelBuilder modelBuilder); } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj b/src/implementations/persistence/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj similarity index 94% rename from src/implementations/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj index 4e63596d..3916c79d 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj @@ -30,7 +30,7 @@ - + \ No newline at end of file diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence/DbContextExtensions.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/DbContextExtensions.cs new file mode 100644 index 00000000..9d4be5db --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/DbContextExtensions.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Extensions; +using Backend.Fx.Logging; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace Backend.Fx.EfCorePersistence +{ + public static class DbContextExtensions + { + private static readonly ILogger Logger = LogManager.Create(typeof(DbContextExtensions)); + + public static void RegisterRowVersionProperty(this ModelBuilder modelBuilder) + { + modelBuilder.Model + .GetEntityTypes() + .Where(mt => typeof(Entity).GetTypeInfo().IsAssignableFrom(mt.ClrType.GetTypeInfo())) + .ForAll(mt => modelBuilder.Entity(mt.ClrType).Property("RowVersion").IsRowVersion()); + } + + public static void RegisterEntityIdAsNeverGenerated(this ModelBuilder modelBuilder) + { + modelBuilder.Model + .GetEntityTypes() + .Where(mt => typeof(Entity).GetTypeInfo().IsAssignableFrom(mt.ClrType.GetTypeInfo())) + .ForAll(mt => modelBuilder.Entity(mt.ClrType).Property(nameof(Entity.Id)).ValueGeneratedNever()); + } + + public static void ApplyAggregateMappings(this DbContext dbContext, ModelBuilder modelBuilder) + { + //CAVE: IAggregateMapping implementations must reside in the same assembly as the Applications DbContext-type + IEnumerable aggregateDefinitionTypeInfos = dbContext + .GetType() + .GetTypeInfo() + .Assembly + .ExportedTypes + .Select(t => t.GetTypeInfo()) + .Where( + t => t.IsClass && !t.IsAbstract && !t.IsGenericType && + typeof(IAggregateMapping).GetTypeInfo().IsAssignableFrom(t)); + foreach (var typeInfo in aggregateDefinitionTypeInfos) + { + var aggregateMapping = (IAggregateMapping)Activator.CreateInstance(typeInfo.AsType()); + aggregateMapping.ApplyEfMapping(modelBuilder); + } + } + + public static void TraceChangeTrackerState(this DbContext dbContext) + { + if (Logger.IsTraceEnabled()) + { + try + { + EntityEntry[] added = dbContext.ChangeTracker.Entries() + .Where(entry => entry.State == EntityState.Added) + .ToArray(); + EntityEntry[] modified = dbContext.ChangeTracker.Entries() + .Where(entry => entry.State == EntityState.Modified) + .ToArray(); + EntityEntry[] deleted = dbContext.ChangeTracker.Entries() + .Where(entry => entry.State == EntityState.Deleted) + .ToArray(); + EntityEntry[] unchanged = dbContext.ChangeTracker.Entries() + .Where(entry => entry.State == EntityState.Unchanged) + .ToArray(); + + var stateDumpBuilder = new StringBuilder(); + stateDumpBuilder.AppendFormat( + "{0} entities added{1}{2}", + added.Length, + deleted.Length == 0 ? "." : ":", + System.Environment.NewLine); + foreach (var entry in added) + { + stateDumpBuilder.AppendFormat( + "added: {0}[{1}]{2}", + entry.Entity.GetType().Name, + GetPrimaryKeyValue(entry), + System.Environment.NewLine); + } + + stateDumpBuilder.AppendFormat( + "{0} entities modified{1}{2}", + modified.Length, + deleted.Length == 0 ? "." : ":", + System.Environment.NewLine); + foreach (var entry in modified) + { + stateDumpBuilder.AppendFormat( + "modified: {0}[{1}]{2}", + entry.Entity.GetType().Name, + GetPrimaryKeyValue(entry), + System.Environment.NewLine); + } + + stateDumpBuilder.AppendFormat( + "{0} entities deleted{1}{2}", + deleted.Length, + deleted.Length == 0 ? "." : ":", + System.Environment.NewLine); + foreach (var entry in deleted) + { + stateDumpBuilder.AppendFormat( + "deleted: {0}[{1}]{2}", + entry.Entity.GetType().Name, + GetPrimaryKeyValue(entry), + System.Environment.NewLine); + } + + stateDumpBuilder.AppendFormat( + "{0} entities unchanged{1}{2}", + unchanged.Length, + deleted.Length == 0 ? "." : ":", + System.Environment.NewLine); + foreach (var entry in unchanged) + { + stateDumpBuilder.AppendFormat( + "unchanged: {0}[{1}]{2}", + entry.Entity.GetType().Name, + GetPrimaryKeyValue(entry), + System.Environment.NewLine); + } + + Logger.Trace(stateDumpBuilder.ToString()); + } + catch (Exception ex) + { + Logger.Warn(ex, "Change tracker state could not be dumped"); + } + } + } + + private static string GetPrimaryKeyValue(EntityEntry entry) + { + return (entry.Entity as Entity)?.Id.ToString(CultureInfo.InvariantCulture) ?? "?"; + } + } +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/DbContextTransactionOperationDecorator.cs similarity index 63% rename from src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/DbContextTransactionOperationDecorator.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/DbContextTransactionOperationDecorator.cs index 6dedd0be..bfc07c43 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/DbContextTransactionOperationDecorator.cs @@ -4,13 +4,16 @@ using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCorePersistence { public class DbContextTransactionOperationDecorator : DbTransactionOperationDecorator { private readonly DbContext _dbContext; - - public DbContextTransactionOperationDecorator(DbContext dbContext, IDbConnection dbConnection, IOperation operation) + + public DbContextTransactionOperationDecorator( + DbContext dbContext, + IDbConnection dbConnection, + IOperation operation) : base(dbConnection, operation) { _dbContext = dbContext; @@ -19,7 +22,7 @@ public DbContextTransactionOperationDecorator(DbContext dbContext, IDbConnection public override void Begin() { base.Begin(); - _dbContext.Database.UseTransaction((DbTransaction) CurrentTransaction); + _dbContext.Database.UseTransaction((DbTransaction)CurrentTransaction); } } -} \ No newline at end of file +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCorePersistenceSession.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCorePersistenceSession.cs new file mode 100644 index 00000000..59cdac6d --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCorePersistenceSession.cs @@ -0,0 +1,249 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Exceptions; +using Backend.Fx.Extensions; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Backend.Fx.EfCorePersistence +{ + public class EfCorePersistenceSession : IPersistenceSession + { + private static readonly ILogger Logger = LogManager.Create(); + private bool _isReadonly; + + public EfCorePersistenceSession(DbContext dbContext, ICurrentTHolder identityHolder, IClock clock) + { + DbContext = dbContext; + Logger.Debug( + "Disabling auto detect changes on this DbContext. Changes will be detected explicitly when flushing."); + DbContext.ChangeTracker.AutoDetectChangesEnabled = false; + IdentityHolder = identityHolder; + Clock = clock; + } + + public DbContext DbContext { get; } + + public ICurrentTHolder IdentityHolder { get; } + + public IClock Clock { get; } + + public void MakeReadonly() + { + Logger.Debug("Making this DbContext readonly. Changes won't be detected when flushing."); + DbContext.ChangeTracker.AutoDetectChangesEnabled = false; + DbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + _isReadonly = true; + } + + public void Flush() + { + if (_isReadonly) + { + Logger.Info("skipping flush because this instance was marked as readonly"); + return; + } + + DetectChanges(); + UpdateTrackingProperties(); + DbContext.TraceChangeTrackerState(); + CheckForMissingTenantIds(); + SaveChanges(); + } + + private void DetectChanges() + { + using (Logger.DebugDuration("Detecting changes")) + { + DbContext.ChangeTracker.DetectChanges(); + } + } + + private void UpdateTrackingProperties() + { + using (Logger.DebugDuration("Updating tracking properties of created and modified entities")) + { + UpdateTrackingProperties(IdentityHolder.Current.Name, Clock.UtcNow); + } + } + + private void CheckForMissingTenantIds() + { + using (Logger.DebugDuration("Checking for missing tenant ids")) + { + AggregateRoot[] aggregatesWithoutTenantId = DbContext + .ChangeTracker + .Entries() + .Where(e => e.State == EntityState.Added) + .Select(e => e.Entity) + .OfType() + .Where(ent => ent.TenantId == 0) + .ToArray(); + if (aggregatesWithoutTenantId.Length > 0) + { + throw new InvalidOperationException( + $"Attempt to save aggregate root entities without tenant id: {string.Join(",", aggregatesWithoutTenantId.Select(agg => agg.DebuggerDisplay))}"); + } + } + } + + private void SaveChanges() + { + using (Logger.DebugDuration("Saving changes")) + { + try + { + DbContext.SaveChanges(); + } + catch (DbUpdateConcurrencyException concurrencyException) + { + throw new ConflictedException( + "Saving changes failed due to optimistic concurrency violation", + concurrencyException); + } + } + } + + private void UpdateTrackingProperties(string userId, DateTime utcNow) + { + userId ??= "anonymous"; + bool isTraceEnabled = Logger.IsTraceEnabled(); + var count = 0; + + // Modifying an entity (also removing an entity from an aggregate) should leave the aggregate root as modified + DbContext.ChangeTracker + .Entries() + .Where( + entry => entry.State == EntityState.Added || entry.State == EntityState.Modified || + entry.State == EntityState.Deleted) + .Where(entry => !(entry.Entity is AggregateRoot)) + .ToArray() + .ForAll( + entry => + { + var aggregateRootEntry = GetAggregateRootEntry(DbContext.ChangeTracker, entry); + if (aggregateRootEntry.State == EntityState.Unchanged) + { + aggregateRootEntry.State = EntityState.Modified; + } + }); + + DbContext.ChangeTracker + .Entries() + .Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified) + .ForAll( + entry => + { + try + { + count++; + var entity = entry.Entity; + + if (entry.State == EntityState.Added) + { + if (isTraceEnabled) + { + Logger.Trace( + "tracking that {0}[{1}] was created by {2} at {3:T} UTC", + entity.GetType().Name, + entity.Id, + userId, + utcNow); + } + + entity.SetCreatedProperties(userId, utcNow); + } + else if (entry.State == EntityState.Modified) + { + if (isTraceEnabled) + { + Logger.Trace( + "tracking that {0}[{1}] was modified by {2} at {3:T} UTC", + entity.GetType().Name, + entity.Id, + userId, + utcNow); + } + + entity.SetModifiedProperties(userId, utcNow); + + // this line causes the recent changes of tracking properties to be detected before flushing + entry.State = EntityState.Modified; + } + } + catch (Exception ex) + { + Logger.Warn(ex, "Updating tracking properties failed"); + throw; + } + }); + if (count > 0) + { + Logger.Debug($"Tracked {count} entities as created/changed on {utcNow:u} by {userId}"); + } + } + + /// + /// This method gets the EntityEntry<AggregateRoot> of an EntityEntry<Entity> + /// assuming it has been loaded and is being tracked by the change tracker. + /// + [return: NotNull] + private static EntityEntry GetAggregateRootEntry(ChangeTracker changeTracker, EntityEntry entry) + { + var entityIdentifier = $"{entry.Entity.GetType().Name}[{(entry.Entity as Identified)?.Id}]"; + Logger.Debug($"Searching aggregate root of {entityIdentifier}"); + foreach (var navigation in entry.Navigations) + { + var navTargetTypeInfo = navigation.Metadata.TargetEntityType.ClrType.GetTypeInfo(); + int navigationTargetForeignKeyValue; + + if (navigation.CurrentValue == null) + { + var navigationMetadata = (INavigation)navigation.Metadata; + // orphaned entity, original value contains the foreign key value + if (navigationMetadata.ForeignKey.Properties.Count > 1) + { + throw new InvalidOperationException("Foreign Keys with multiple properties are not supported."); + } + + var property = navigationMetadata.ForeignKey.Properties[0]; + navigationTargetForeignKeyValue = (int)entry.OriginalValues[property]; + } + else + { + // added or modified entity, current value contains the foreign key value + navigationTargetForeignKeyValue = ((Entity)navigation.CurrentValue).Id; + } + + // assumption: an entity cannot be loaded on its own. Everything on the navigation path starting from the + // aggregate root must have been loaded before, therefore we can find it using the change tracker + EntityEntry navigationTargetEntry = changeTracker + .Entries() + .Single( + ent => Equals(ent.Entity.GetType().GetTypeInfo(), navTargetTypeInfo) + && ent.Property(nameof(Entity.Id)).CurrentValue.Equals(navigationTargetForeignKeyValue)); + + // if the target is AggregateRoot, no (further) recursion is needed + if (typeof(AggregateRoot).GetTypeInfo().IsAssignableFrom(navTargetTypeInfo)) + { + return navigationTargetEntry; + } + + // recurse in case of "Entity -> Entity -> AggregateRoot" + Logger.Debug("Recursing..."); + return GetAggregateRootEntry(changeTracker, navigationTargetEntry); + } + + throw new InvalidOperationException($"Could not find aggregate root of {entityIdentifier}"); + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCoreScopedServices.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCoreScopedServices.cs new file mode 100644 index 00000000..7b9a21de --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCoreScopedServices.cs @@ -0,0 +1,84 @@ +using System; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Patterns.DependencyInjection.Pure; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCorePersistence +{ + public abstract class EfCoreScopedServices : ScopedServices where TDbContext : DbContext + { + protected EfCoreScopedServices( + TDbContext dbContext, + IClock clock, + IIdentity identity, + TenantId tenantId, + params Assembly[] assemblies) + : base(clock, identity, tenantId, assemblies) + { + DbContext = dbContext; + } + + public TDbContext DbContext { get; } + + public IDbConnection DbConnection + { + get + { + var dbConnection = DbContext.Database.GetDbConnection(); + if (dbConnection.State == ConnectionState.Closed) + { + dbConnection.Open(); + } + + return dbConnection; + } + } + + public override IAsyncRepository GetAsyncRepository() + { + return (IAsyncRepository)GetRepository(typeof(TAggregateRoot)); + } + + public override IRepository GetRepository() + { + return (IRepository)GetRepository(typeof(TAggregateRoot)); + } + + private object GetRepository(Type aggregateRootType) + { + object aggregateAuthorization = GetAggregateAuthorization(IdentityHolder, aggregateRootType); + var efRepositoryType = typeof(EfRepository<>).MakeGenericType(aggregateRootType); + return Activator.CreateInstance( + efRepositoryType, + ((EfCorePersistenceSession)CanFlush).DbContext, + GetAggregateMapping(aggregateRootType), + TenantIdHolder, + aggregateAuthorization); + } + + protected IAggregateMapping GetAggregateMapping(Type aggregateRootType) + { + var aggregateDefinitionType = typeof(TDbContext) + .Assembly + .GetTypes() + .Where(t => t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract) + .SingleOrDefault( + t => + typeof(IAggregateMapping<>).MakeGenericType(aggregateRootType) + .GetTypeInfo() + .IsAssignableFrom(t)); + if (aggregateDefinitionType == null) + { + throw new InvalidOperationException($"No Aggregate Definition for {aggregateRootType.Name} found"); + } + + return (IAggregateMapping)Activator.CreateInstance(aggregateDefinitionType); + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCoreSingletonServices.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCoreSingletonServices.cs new file mode 100644 index 00000000..b8080d63 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfCoreSingletonServices.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using System.Reflection; +using Backend.Fx.Patterns.DependencyInjection.Pure; +using Backend.Fx.Patterns.IdGeneration; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCorePersistence +{ + public abstract class EfCoreSingletonServices : SingletonServices + where TScopedServices : IScopedServices where TDbContext : DbContext + { + protected EfCoreSingletonServices( + string connectionString, + DbContextOptions dbContextOptions, + IEntityIdGenerator entityIdGenerator, + params Assembly[] assemblies) + : base( + (assemblies ?? Array.Empty()).Concat(new[] { typeof(EfCoreSingletonServices<,>).Assembly }) + .ToArray()) + { + ConnectionString = connectionString; + DbContextOptions = dbContextOptions; + EntityIdGenerator = entityIdGenerator; + } + + public DbContextOptions DbContextOptions { get; } + + public string ConnectionString { get; } + + public override IEntityIdGenerator EntityIdGenerator { get; } + } +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfRepository.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfRepository.cs similarity index 65% rename from src/implementations/Backend.Fx.EfCorePersistence/EfRepository.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/EfRepository.cs index 304799b4..03e8bd6a 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfRepository.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EfRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Backend.Fx.BuildingBlocks; @@ -10,49 +11,55 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Backend.Fx.EfCorePersistence { - public class EfRepository : Repository, IAsyncRepository where TAggregateRoot : AggregateRoot + public class EfRepository : Repository, IAsyncRepository + where TAggregateRoot : AggregateRoot { private static readonly ILogger Logger = LogManager.Create>(); private readonly IAggregateAuthorization _aggregateAuthorization; private readonly IAggregateMapping _aggregateMapping; - private DbContext _dbContext; [SuppressMessage("ReSharper", "EF1001")] - public EfRepository([CanBeNull] DbContext dbContext, IAggregateMapping aggregateMapping, - ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) + public EfRepository( + [JetBrains.Annotations.NotNull] DbContext dbContext, + IAggregateMapping aggregateMapping, + ICurrentTHolder currentTenantIdHolder, + IAggregateAuthorization aggregateAuthorization) : base(currentTenantIdHolder, aggregateAuthorization) { - _dbContext = dbContext; + DbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _aggregateMapping = aggregateMapping; _aggregateAuthorization = aggregateAuthorization; // somewhat a hack: using the internal EF Core services against advice - var localViewListener = dbContext?.GetService(); + var localViewListener = dbContext.GetService(); localViewListener?.RegisterView(AuthorizeChanges); } - [SuppressMessage("ReSharper", "EF1001")] - public DbContext DbContext + public DbContext DbContext { get; } + + protected override IQueryable RawAggregateQueryable { - get => _dbContext ?? throw new InvalidOperationException( - "This EfRepository does not have a DbContext yet. You might either make sure a proper DbContext gets injected or the DbContext is initialized later using a derived class") - ; - protected set + get { - if (_dbContext != null) throw new InvalidOperationException("This EfRepository has already a DbContext assigned. It is not allowed to change it later."); - _dbContext = value; - var localViewListener = _dbContext?.GetService(); - localViewListener?.RegisterView(AuthorizeChanges); + IQueryable queryable = DbContext.Set(); + if (_aggregateMapping.IncludeDefinitions != null) + { + foreach (Expression> include in _aggregateMapping.IncludeDefinitions) + { + queryable = queryable.Include(include); + } + } + + return queryable; } } - + public async Task SingleAsync(int id, CancellationToken cancellationToken = default) { return await AggregateQueryable.SingleAsync(agg => agg.Id == id, cancellationToken); @@ -73,40 +80,38 @@ public async Task AnyAsync(CancellationToken cancellationToken = default) return await AggregateQueryable.AnyAsync(cancellationToken); } - public async Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default) + public async Task ResolveAsync( + IEnumerable ids, + CancellationToken cancellationToken = default) { if (ids == null) { - return new TAggregateRoot[0]; + return Array.Empty(); } int[] idsToResolve = ids as int[] ?? ids.ToArray(); - TAggregateRoot[] resolved = await AggregateQueryable.Where(agg => idsToResolve.Contains(agg.Id)).ToArrayAsync(cancellationToken); + TAggregateRoot[] resolved = await AggregateQueryable.Where(agg => idsToResolve.Contains(agg.Id)) + .ToArrayAsync(cancellationToken); if (resolved.Length != idsToResolve.Length) { - throw new ArgumentException($"The following {AggregateTypeName} ids could not be resolved: {string.Join(", ", idsToResolve.Except(resolved.Select(agg => agg.Id)))}"); + throw new ArgumentException( + $"The following {AggregateTypeName} ids could not be resolved: {string.Join(", ", idsToResolve.Except(resolved.Select(agg => agg.Id)))}"); } - return resolved; - } - protected override IQueryable RawAggregateQueryable - { - get - { - IQueryable queryable = DbContext.Set(); - if (_aggregateMapping.IncludeDefinitions != null) - foreach (var include in _aggregateMapping.IncludeDefinitions) - queryable = queryable.Include(include); - return queryable; - } + return resolved; } /// - /// Due to the fact, that a real lifecycle hook API is not yet available (see issue https://github.com/aspnet/EntityFrameworkCore/issues/626) - /// we are using an internal service to achieve the same goal: When a state change occurs from unchanged to modified, and this repository is - /// handling this type of aggregate, we're calling IAggregateAuthorization.CanModify to enforce user privilege checking. - /// We're accepting the possible instability of EF Core internals due to the fact that there is a full API feature in the pipeline that will - /// make this workaround obsolete. Also, not much of an effort was done to write this code, so if we have to deal with this issue in the future + /// Due to the fact, that a real lifecycle hook API is not yet available (see issue + /// https://github.com/aspnet/EntityFrameworkCore/issues/626) + /// we are using an internal service to achieve the same goal: When a state change occurs from unchanged to modified, + /// and this repository is + /// handling this type of aggregate, we're calling IAggregateAuthorization.CanModify to enforce user privilege + /// checking. + /// We're accepting the possible instability of EF Core internals due to the fact that there is a full API feature in + /// the pipeline that will + /// make this workaround obsolete. Also, not much of an effort was done to write this code, so if we have to deal with + /// this issue in the future /// again, we do not loose a lot. /// /// @@ -114,11 +119,16 @@ protected override IQueryable RawAggregateQueryable [SuppressMessage("ReSharper", "EF1001")] private void AuthorizeChanges(InternalEntityEntry entry, EntityState previousState) { - if (previousState == EntityState.Unchanged && entry.EntityState == EntityState.Modified && entry.EntityType.ClrType == typeof(TAggregateRoot)) + if (previousState == EntityState.Unchanged && entry.EntityState == EntityState.Modified && + entry.EntityType.ClrType == typeof(TAggregateRoot)) { - var aggregateRoot = (TAggregateRoot) entry.Entity; - if (!_aggregateAuthorization.CanModify(aggregateRoot)) throw new ForbiddenException("Unauthorized attempt to modify {AggregateTypeName}[{aggregateRoot.Id}]") - .AddError($"You are not allowed to modify {AggregateTypeName}[{aggregateRoot.Id}]"); + var aggregateRoot = (TAggregateRoot)entry.Entity; + if (!_aggregateAuthorization.CanModify(aggregateRoot)) + { + throw new ForbiddenException( + "Unauthorized attempt to modify {AggregateTypeName}[{aggregateRoot.Id}]") + .AddError($"You are not allowed to modify {AggregateTypeName}[{aggregateRoot.Id}]"); + } } } @@ -140,4 +150,4 @@ protected override void DeletePersistent(TAggregateRoot aggregateRoot) DbContext.Set().Remove(aggregateRoot); } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EntityQueryable.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EntityQueryable.cs similarity index 51% rename from src/implementations/Backend.Fx.EfCorePersistence/EntityQueryable.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/EntityQueryable.cs index 48d4a959..ea20dfd7 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EntityQueryable.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/EntityQueryable.cs @@ -11,24 +11,13 @@ namespace Backend.Fx.EfCorePersistence { public class EntityQueryable : IQueryable where TEntity : Entity { - [CanBeNull] private DbContext _dbContext; - - public EntityQueryable(DbContext dbContext) + public EntityQueryable([NotNull] DbContext dbContext) { - _dbContext = dbContext; + DbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } - public DbContext DbContext - { - get => _dbContext ?? throw new InvalidOperationException( - "This EntityQueryable does not have a DbContext yet. You might either make sure a proper DbContext gets injected or the DbContext is initialized later using a derived class") - ; - protected set - { - if (_dbContext != null) throw new InvalidOperationException("This EntityQueryable has already a DbContext assigned. It is not allowed to change it later."); - _dbContext = value; - } - } + [NotNull] + public DbContext DbContext { get; } private IQueryable InnerQueryable => DbContext.Set(); @@ -39,7 +28,7 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable) InnerQueryable).GetEnumerator(); + return ((IEnumerable)InnerQueryable).GetEnumerator(); } public Type ElementType => InnerQueryable.ElementType; @@ -48,4 +37,4 @@ IEnumerator IEnumerable.GetEnumerator() public IQueryProvider Provider => InnerQueryable.Provider; } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/IAggregateMapping.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/IAggregateMapping.cs similarity index 99% rename from src/implementations/Backend.Fx.EfCorePersistence/IAggregateMapping.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/IAggregateMapping.cs index 76dd9fd5..317de002 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/IAggregateMapping.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/IAggregateMapping.cs @@ -11,8 +11,9 @@ public interface IAggregateMapping void ApplyEfMapping(ModelBuilder modelBuilder); } + public interface IAggregateMapping : IAggregateMapping where T : AggregateRoot { IEnumerable>> IncludeDefinitions { get; } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs similarity index 80% rename from src/implementations/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs index 2bf4efec..8b8735b4 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs @@ -9,10 +9,10 @@ namespace Backend.Fx.EfCorePersistence public class PlainAggregateMapping : AggregateMapping where TAggregateRoot : AggregateRoot { - public override IEnumerable>> IncludeDefinitions => new Expression>[0]; + public override IEnumerable>> IncludeDefinitions => + Array.Empty>>(); public override void ApplyEfMapping(ModelBuilder modelBuilder) - { - } + { } } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.RabbitMq/Properties/AssemblyInfo.cs b/src/implementations/persistence/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs similarity index 74% rename from src/implementations/Backend.Fx.RabbitMq/Properties/AssemblyInfo.cs rename to src/implementations/persistence/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs index 5f565d7d..43603728 100644 --- a/src/implementations/Backend.Fx.RabbitMq/Properties/AssemblyInfo.cs +++ b/src/implementations/persistence/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyVersion("0.0.0.0")] [assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj similarity index 87% rename from src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj rename to src/implementations/persistence/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj index dd5b6e40..160cde27 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj @@ -8,7 +8,8 @@ false false false - false + false + latest @@ -25,8 +26,11 @@ - + + + + diff --git a/src/implementations/persistence/Backend.Fx.InMemoryPersistence/IInMemoryStores.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/IInMemoryStores.cs new file mode 100644 index 00000000..742d391e --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/IInMemoryStores.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Concurrent; +using Backend.Fx.BuildingBlocks; + +namespace Backend.Fx.InMemoryPersistence +{ + public interface IInMemoryStores + { + InMemoryStore GetStore() where T : AggregateRoot; + } + + + public class InMemoryStores : IInMemoryStores + { + private readonly ConcurrentDictionary _dictionaries = new(); + + public InMemoryStore GetStore() where T : AggregateRoot + { + return (InMemoryStore)_dictionaries.GetOrAdd(typeof(T), _ => new InMemoryStore()); + } + } +} diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs similarity index 99% rename from src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs rename to src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs index 1f9f54fd..6d7c01be 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs @@ -11,4 +11,4 @@ public int NextId() return _nextId++; } } -} \ No newline at end of file +} diff --git a/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryPersistenceSession.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryPersistenceSession.cs new file mode 100644 index 00000000..ac1200c8 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryPersistenceSession.cs @@ -0,0 +1,26 @@ +using System.Security.Principal; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.InMemoryPersistence +{ + public class InMemoryPersistenceSession : IPersistenceSession + { + public InMemoryPersistenceSession(ICurrentTHolder identityHolder, AdjustableClock clock) + { + IdentityHolder = identityHolder; + Clock = clock; + } + + public void Flush() + { } + + public ICurrentTHolder IdentityHolder { get; } + + public IClock Clock { get; } + + public void MakeReadonly() + { } + } +} diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs similarity index 86% rename from src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs rename to src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs index 9797c793..b38270ac 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs @@ -11,12 +11,11 @@ public class InMemoryQueryable : IQueryable wher { private readonly IQueryable _queryableImplementation; - public InMemoryQueryable(IInMemoryStore store) + public InMemoryQueryable(InMemoryStore store) { _queryableImplementation = store.Values.AsQueryable(); } - public IEnumerator GetEnumerator() { return _queryableImplementation.GetEnumerator(); @@ -24,7 +23,7 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable) _queryableImplementation).GetEnumerator(); + return ((IEnumerable)_queryableImplementation).GetEnumerator(); } public Type ElementType => _queryableImplementation.ElementType; @@ -33,4 +32,4 @@ IEnumerator IEnumerable.GetEnumerator() public IQueryProvider Provider => _queryableImplementation.Provider; } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs similarity index 82% rename from src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs rename to src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs index 07708f7a..e5db1e8a 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs @@ -10,13 +10,16 @@ namespace Backend.Fx.InMemoryPersistence { public class InMemoryRepository : Repository where T : AggregateRoot { - public InMemoryRepository(IInMemoryStore store, ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) + public InMemoryRepository( + InMemoryStore store, + ICurrentTHolder currentTenantIdHolder, + IAggregateAuthorization aggregateAuthorization) : base(currentTenantIdHolder, aggregateAuthorization) { Store = store; } - public virtual IInMemoryStore Store { get; } + public virtual InMemoryStore Store { get; } protected override IQueryable RawAggregateQueryable => Store.Values.AsQueryable(); @@ -45,4 +48,4 @@ protected override void DeletePersistent(T aggregateRoot) Store.Remove(aggregateRoot.Id); } } -} \ No newline at end of file +} diff --git a/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryScopedServices.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryScopedServices.cs new file mode 100644 index 00000000..181b1cb1 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryScopedServices.cs @@ -0,0 +1,42 @@ +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DependencyInjection.Pure; + +namespace Backend.Fx.InMemoryPersistence +{ + public abstract class InMemoryScopedServices : ScopedServices + { + private readonly IInMemoryStores _inMemoryStores; + + protected InMemoryScopedServices( + IClock clock, + IIdentity identity, + TenantId tenantId, + Assembly[] assemblies, + IInMemoryStores inMemoryStores) + : base(clock, identity, tenantId, assemblies) + { + _inMemoryStores = inMemoryStores; + } + + protected override IPersistenceSession CreatePersistenceSession() + { + return new InMemoryPersistenceSession(IdentityHolder, Clock); + } + + public override IRepository GetRepository() + { + return new InMemoryRepository( + _inMemoryStores.GetStore(), + TenantIdHolder, + (IAggregateAuthorization)GetAggregateAuthorization( + IdentityHolder, + typeof(TAggregateRoot))); + } + } +} diff --git a/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemorySingletonServices.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemorySingletonServices.cs new file mode 100644 index 00000000..e4550bb5 --- /dev/null +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemorySingletonServices.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using Backend.Fx.Patterns.DependencyInjection.Pure; +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.InMemoryPersistence +{ + public abstract class InMemorySingletonServices + : SingletonServices + where TInMemoryScopedServices : IScopedServices + { + protected InMemorySingletonServices(params Assembly[] assemblies) : base(assemblies) + { + Stores = new InMemoryStores(); + } + + public InMemoryStores Stores { get; } + + public override IEntityIdGenerator EntityIdGenerator { get; } = new InMemoryEntityIdGenerator(); + } +} diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryStore.cs similarity index 89% rename from src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs rename to src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryStore.cs index 67a5d975..c109f0a9 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryStore.cs @@ -4,11 +4,7 @@ namespace Backend.Fx.InMemoryPersistence { - public interface IInMemoryStore : IDictionary where T : AggregateRoot - { - } - - public class InMemoryStore : IInMemoryStore where T : AggregateRoot + public class InMemoryStore : IDictionary where T : AggregateRoot { private readonly IDictionary _dictionaryImplementation = new Dictionary(); @@ -19,7 +15,7 @@ public IEnumerator> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable) _dictionaryImplementation).GetEnumerator(); + return ((IEnumerable)_dictionaryImplementation).GetEnumerator(); } public void Add(KeyValuePair item) @@ -81,4 +77,4 @@ public T this[int key] public ICollection Values => _dictionaryImplementation.Values; } -} \ No newline at end of file +} diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryView.cs similarity index 92% rename from src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs rename to src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryView.cs index cd4ff074..3c0ae17b 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs +++ b/src/implementations/persistence/Backend.Fx.InMemoryPersistence/InMemoryView.cs @@ -23,11 +23,13 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable) _list).GetEnumerator(); + return ((IEnumerable)_list).GetEnumerator(); } public Type ElementType => typeof(T); + public Expression Expression => _list.AsQueryable().Expression; + public IQueryProvider Provider => _list.AsQueryable().Provider; } -} \ No newline at end of file +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DbConnectionEx.cs b/tests/Backend.Fx.EfCorePersistence.Tests/DbConnectionEx.cs deleted file mode 100644 index 499d8a51..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DbConnectionEx.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using JetBrains.Annotations; - -namespace Backend.Fx.EfCorePersistence.Tests -{ - public static class DbConnectionEx - { - public static void ExecuteNonQuery(this IDbConnection openConnection, string cmd) - { - using (IDbCommand command = openConnection.CreateCommand()) - { - command.CommandText = cmd; - command.ExecuteNonQuery(); - } - } - - public static T ExecuteScalar(this IDbConnection openConnection, string cmd) - { - using (IDbCommand command = openConnection.CreateCommand()) - { - command.CommandText = cmd; - object scalarResult = command.ExecuteScalar(); - if (typeof(T) == typeof(int)) return (T) (object) Convert.ToInt32(scalarResult); - return (T) scalarResult; - } - } - - [UsedImplicitly] - public static IEnumerable ExecuteReader(this IDbConnection openConnection, string cmd, Func forEachResultFunc) - { - using (IDbCommand command = openConnection.CreateCommand()) - { - command.CommandText = cmd; - IDataReader reader = command.ExecuteReader(); - while (reader.NextResult()) yield return forEachResultFunc(reader); - } - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/DatabaseFixture.cs deleted file mode 100644 index 59578f8c..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/DatabaseFixture.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Data; -using System.Security.Principal; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures -{ - public abstract class DatabaseFixture - { - public void CreateDatabase() - { - using (var dbContext = new TestDbContext(GetDbContextOptionsForDbCreation())) - { - dbContext.Database.EnsureCreated(); - } - } - - protected abstract DbContextOptions GetDbContextOptionsForDbCreation(); - - public abstract DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection); - - public abstract DbConnectionOperationDecorator UseOperation(); - - public TestDbSession CreateTestDbSession(DbConnectionOperationDecorator operation = null, IIdentity asIdentity = null, IClock clock = null) - { - CurrentIdentityHolder CreateAsIdentity() - { - var cih = new CurrentIdentityHolder(); - cih.ReplaceCurrent(asIdentity); - return cih; - } - - clock ??= new WallClock(); - operation ??= UseOperation(); - - operation.Begin(); - - var identityHolder = asIdentity == null - ? CurrentIdentityHolder.CreateSystem() - : CreateAsIdentity(); - return new TestDbSession(this, operation, identityHolder, clock); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqlServerDatabaseFixture.cs deleted file mode 100644 index 5f57d5b2..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures -{ - [Obsolete("Not supported on build agents")] - public class SqlServerDatabaseFixture : DatabaseFixture - { - private static int _testindex = 1; - private readonly string _connectionString; - - public SqlServerDatabaseFixture() - { - var dbName = $"TestFixture_{_testindex++:000}"; - var sqlConnectionStringBuilder = new SqlConnectionStringBuilder("Server=.\\SQLExpress;Trusted_Connection=True;"); - using (IDbConnection connection = new SqlConnection(sqlConnectionStringBuilder.ConnectionString)) - { - connection.Open(); - - using (IDbCommand dropCommand = connection.CreateCommand()) - { - dropCommand.CommandText = $"IF EXISTS(SELECT * FROM sys.Databases WHERE Name='{dbName}') ALTER DATABASE [{dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"; - dropCommand.ExecuteNonQuery(); - } - - using (IDbCommand dropCommand = connection.CreateCommand()) - { - dropCommand.CommandText = $"IF EXISTS(SELECT * FROM sys.Databases WHERE Name='{dbName}') DROP DATABASE [{dbName}]"; - dropCommand.ExecuteNonQuery(); - } - - using (IDbCommand createCommand = connection.CreateCommand()) - { - createCommand.CommandText = $"CREATE DATABASE [{dbName}]"; - createCommand.ExecuteNonQuery(); - } - - connection.Close(); - } - - sqlConnectionStringBuilder.InitialCatalog = dbName; - _connectionString = sqlConnectionStringBuilder.ConnectionString; - } - - protected override DbContextOptions GetDbContextOptionsForDbCreation() - { - return new DbContextOptionsBuilder().UseSqlServer(_connectionString).Options; - } - - - public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) - { - return new DbContextOptionsBuilder().UseSqlServer((SqlConnection) connection); - } - - public override DbConnectionOperationDecorator UseOperation() - { - var sqliteConnection = new SqlConnection(_connectionString); - IOperation operation = new Operation(); - operation = new DbTransactionOperationDecorator(sqliteConnection, operation); - return new DbConnectionOperationDecorator(sqliteConnection, operation); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqliteDatabaseFixture.cs deleted file mode 100644 index 18440c2c..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Data; -using System.IO; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures -{ - public class SqliteDatabaseFixture : DatabaseFixture - { - private readonly string _connectionString = "Data Source=" + Path.GetTempFileName(); - - protected override DbContextOptions GetDbContextOptionsForDbCreation() - { - return new DbContextOptionsBuilder().UseSqlite(_connectionString).Options; - } - - public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) - { - return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection); - } - - public override DbConnectionOperationDecorator UseOperation() - { - var sqliteConnection = new SqliteConnection(_connectionString); - IOperation operation = new Operation(); - operation = new DbTransactionOperationDecorator(sqliteConnection, operation); - return new DbConnectionOperationDecorator(sqliteConnection, operation); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/TestDbSession.cs deleted file mode 100644 index 8fe47f7f..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/TestDbSession.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Data; -using System.Security.Principal; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures -{ - public class TestDbSession : ICanFlush, IDisposable - { - private readonly DbConnectionOperationDecorator _operation; - private readonly EfFlush _efFlush; - - public TestDbSession(DatabaseFixture fixture, DbConnectionOperationDecorator operation, ICurrentTHolder identityHolder, IClock clock) - { - _operation = operation; - DbContext = new TestDbContext(fixture.GetDbContextOptionsBuilder(operation.DbConnection).Options); - _efFlush = new EfFlush(DbContext, identityHolder, clock); - DbConnection = operation.DbConnection; - } - - - public TestDbContext DbContext { get; } - public IDbConnection DbConnection { get; } - - public void Flush() - { - _efFlush.Flush(); - } - - public void Dispose() - { - _efFlush.Flush(); - DbContext.Dispose(); - _operation.Complete(); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs deleted file mode 100644 index a0de99dd..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.IdGeneration; -using FakeItEasy; -using Xunit; - -namespace Backend.Fx.EfCorePersistence.Tests -{ - public class TheRepositoryOfComposedAggregate - { - public TheRepositoryOfComposedAggregate() - { - A.CallTo(() => _idGenerator.NextId()).ReturnsLazily(() => _nextId++); - //_fixture = new SqlServerDatabaseFixture(); - _fixture = new SqliteDatabaseFixture(); - _fixture.CreateDatabase(); - } - - private static int _nextTenantId = 57839; - private static int _nextId = 1; - private readonly int _tenantId = _nextTenantId++; - private readonly IEqualityComparer _tolerantDateTimeComparer = new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000)); - private readonly IEntityIdGenerator _idGenerator = A.Fake(); - private readonly DatabaseFixture _fixture; - - private int CreateBlogWithPost(IDbConnection dbConnection, int postCount = 1) - { - { - var blogId = _nextId++; - dbConnection.ExecuteNonQuery( - $"INSERT INTO Blogs (Id, TenantId, Name, CreatedOn, CreatedBy) VALUES ({blogId}, {CurrentTenantIdHolder.Create(_tenantId).Current.Value}, 'my blog', CURRENT_TIMESTAMP, 'persistence test')"); - var count = dbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); - Assert.Equal(1, count); - - for (var i = 0; i < postCount; i++) - dbConnection.ExecuteNonQuery( - $"INSERT INTO Posts (Id, BlogId, Name, TargetAudience_IsPublic, TargetAudience_Culture, CreatedOn, CreatedBy) VALUES ({_nextId++}, {blogId}, 'my post {i:00}', '1', 'de-DE', CURRENT_TIMESTAMP, 'persistence test')"); - - return blogId; - } - } - - //FAILING!!!! - // this shows, that ValueObjects treated as OwnedTypes are not supported very well - //[Fact] - //public void CanUpdateDependantValueObject() - //{ - // using (DbSession dbs = _fixture.UseDbSession()) - // { - // int id = CreateBlogWithPost(dbSession.DbConnection, 10); - // Post post; - - // using (var uow = dbs.UseUnitOfWork(_clock)) - // { - // var sut = new EfRepository(uow.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), - // new AllowAll()); - // var blog = sut.Single(id); - // post = blog.Posts.First(); - // post.TargetAudience = new TargetAudience{Culture = "es-AR", IsPublic = false}; - // uow.Complete(); - // } - - // - // { - // string culture = dbSession.DbConnection.ExecuteScalar($"SELECT TargetAudience_Culture ame FROM Posts where id = {post.Id}"); - // Assert.Equal("es-AR", culture); - - // string strChangedOn = dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); - // DateTime changedOn = DateTime.Parse(strChangedOn); - // Assert.Equal(_clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); - // } - // } - //} - - [Fact] - public void CanAddDependent() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var id = CreateBlogWithPost(dbSession.DbConnection, 10); - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blog blog = sut.Single(id); - blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "added")); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(11, count); - } - } - - [Fact] - public void CanCreate() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); - Assert.Equal(0, count); - - count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(0, count); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - var blog = new Blog(_idGenerator.NextId(), "my blog"); - blog.AddPost(_idGenerator, "my post"); - sut.Add(blog); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); - Assert.Equal(1, count); - - count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(1, count); - } - } - - [Fact] - public void CanDelete() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var id = CreateBlogWithPost(dbSession.DbConnection); - - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blog blog = sut.Single(id); - sut.Delete(blog); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs"); - Assert.Equal(0, count); - - count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(0, count); - } - } - - [Fact] - public void CanDeleteDependent() - { - int id; - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - id = CreateBlogWithPost(dbSession.DbConnection, 10); - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(10, count); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), - CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blog blog = sut.Single(id); - Post firstPost = blog.Posts.First(); - firstPost.SetName("sadfasfsadf"); - blog.Posts.Remove(firstPost); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(9, count); - } - } - - - [Fact] - public void CanRead() - { - int id; - Blog blog; - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - id = CreateBlogWithPost(dbSession.DbConnection); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), - CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - blog = sut.Single(id); - } - - Assert.NotNull(blog); - Assert.Equal(id, blog.Id); - Assert.Equal("my blog", blog.Name); - Assert.NotEmpty(blog.Posts); - } - - - [Fact] - public void CanReplaceDependentCollection() - { - int id; - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - id = CreateBlogWithPost(dbSession.DbConnection, 10); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blog blog = sut.Single(id); - blog.Posts.Clear(); - blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 1")); - blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 2")); - blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 3")); - blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 4")); - blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "new name 5")); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Posts"); - Assert.Equal(5, count); - } - } - - [Fact] - public void CanUpdate() - { - int id; - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - id = CreateBlogWithPost(dbSession.DbConnection); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), - CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blog blog = sut.Single(id); - blog.Modify("modified"); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - Assert.Equal(1, dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs")); - Assert.Equal(id, dbSession.DbConnection.ExecuteScalar("SELECT Id FROM Blogs LIMIT 1")); - Assert.Equal("modified", dbSession.DbConnection.ExecuteScalar("SELECT Name FROM Blogs LIMIT 1")); - Assert.Equal("modified", dbSession.DbConnection.ExecuteScalar("SELECT Name FROM Posts LIMIT 1")); - } - } - - [Fact] - public void CanUpdateDependant() - { - var clock = new AdjustableClock(new WallClock()); - clock.OverrideUtcNow(new DateTime(2020, 01, 20, 20, 30, 40)); - - int id; - using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) - { - id = CreateBlogWithPost(dbSession.DbConnection, 10); - } - - Post post; - - using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), - new AllowAll()); - Blog blog = sut.Single(id); - post = blog.Posts.First(); - post.SetName("modified"); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) - { - var name = dbSession.DbConnection.ExecuteScalar($"SELECT name FROM Posts where id = {post.Id}"); - Assert.Equal("modified", name); - - var strChangedOn = dbSession.DbConnection.ExecuteScalar($"SELECT changedon FROM Posts where id = {post.Id}"); - DateTime changedOn = DateTime.Parse(strChangedOn); - Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); - } - } - - [Fact] - public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() - { - var clock = new AdjustableClock(new WallClock()); - clock.OverrideUtcNow(new DateTime(2020, 01, 20, 20, 30, 40)); - - int id; - using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock:clock)) - { - id = CreateBlogWithPost(dbSession.DbConnection, 10); - } - - DateTime expectedModifiedOn = clock.Advance(TimeSpan.FromHours(1)); - - using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock:clock)) - { - var sut = new EfRepository(dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blog b = sut.Single(id); - b.Posts.Remove(b.Posts.First()); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock:clock)) - { - Blog blog = dbSession.DbContext.Set().Find(id); - Assert.NotNull(blog.ChangedOn); - Assert.Equal(expectedModifiedOn, blog.ChangedOn.Value, _tolerantDateTimeComparer); - } - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs deleted file mode 100644 index c749cba5..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Threading.Tasks; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; -using Xunit; - -namespace Backend.Fx.EfCorePersistence.Tests -{ - public class TheRepositoryOfPlainAggregate - { - public TheRepositoryOfPlainAggregate() - { - //_fixture = new SqlServerDatabaseFixture(); - _fixture = new SqliteDatabaseFixture(); - _fixture.CreateDatabase(); - } - - private static int _nextTenantId = 12312; - private readonly int _tenantId = _nextTenantId++; - private readonly DatabaseFixture _fixture; - - [Fact] - public void CanCreate() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - repo.Add(new Blogger(345, "Metulsky", "Bratislav")); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT Count(*) FROM Bloggers"); - Assert.Equal(1, count); - - count = dbSession.DbConnection.ExecuteScalar( - $"SELECT Count(*) FROM Bloggers WHERE Id=345"); - Assert.Equal(1, count); - } - } - - - [Fact] - public void CanDelete() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - dbSession.DbConnection.ExecuteNonQuery( - "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + - $"VALUES (555, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blogger bratislavMetulsky = repo.Single(555); - repo.Delete(bratislavMetulsky); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar("SELECT Count(*) FROM Bloggers WHERE Id = 555"); - Assert.Equal(0, count); - } - } - - - - [Fact] - public void CanRead() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - dbSession.DbConnection.ExecuteNonQuery( - "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + - $"VALUES (444, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); - - { - var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - - bool any = repo.Any(); - Assert.True(any); - - Blogger[] all = repo.GetAll(); - Assert.NotEmpty(all); - - Blogger bratislavMetulsky = repo.Single(444); - Assert.Equal(_tenantId, bratislavMetulsky.TenantId); - Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); - Assert.Equal("Bratislav", bratislavMetulsky.FirstName); - Assert.Equal("Metulsky", bratislavMetulsky.LastName); - Assert.Equal("whatever", bratislavMetulsky.Bio); - - bratislavMetulsky = repo.SingleOrDefault(444); - Assert.NotNull(bratislavMetulsky); - Assert.Equal(_tenantId, bratislavMetulsky.TenantId); - Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); - Assert.Equal("Bratislav", bratislavMetulsky.FirstName); - Assert.Equal("Metulsky", bratislavMetulsky.LastName); - Assert.Equal("whatever", bratislavMetulsky.Bio); - } - } - } - - - [Fact] - public async Task CanReadAsync() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - dbSession.DbConnection.ExecuteNonQuery( - "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + - $"VALUES (555, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); - - { - var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - - bool any = await repo.AnyAsync(); - Assert.True(any); - - Blogger[] all = await repo.GetAllAsync(); - Assert.NotEmpty(all); - - Blogger bratislavMetulsky = await repo.SingleAsync(555); - Assert.Equal(_tenantId, bratislavMetulsky.TenantId); - Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); - Assert.Equal("Bratislav", bratislavMetulsky.FirstName); - Assert.Equal("Metulsky", bratislavMetulsky.LastName); - Assert.Equal("whatever", bratislavMetulsky.Bio); - - bratislavMetulsky = await repo.SingleOrDefaultAsync(555); - Assert.NotNull(bratislavMetulsky); - Assert.Equal(_tenantId, bratislavMetulsky.TenantId); - Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); - Assert.Equal("Bratislav", bratislavMetulsky.FirstName); - Assert.Equal("Metulsky", bratislavMetulsky.LastName); - Assert.Equal("whatever", bratislavMetulsky.Bio); - } - } - } - - [Fact] - public void CanUpdate() - { - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - dbSession.DbConnection.ExecuteNonQuery( - "INSERT INTO Bloggers (Id, TenantId, CreatedOn, CreatedBy, FirstName, LastName, Bio) " + - $"VALUES (456, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), - CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blogger bratislavMetulsky = repo.Single(456); - bratislavMetulsky.FirstName = "Johnny"; - bratislavMetulsky.LastName = "Flash"; - bratislavMetulsky.Bio = "Der lustige Clown"; - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var count = dbSession.DbConnection.ExecuteScalar( - $"SELECT Count(*) FROM Bloggers WHERE FirstName = 'Johnny' AND LastName = 'Flash' AND TenantId = '{_tenantId}'"); - Assert.Equal(1, count); - } - - using (TestDbSession dbSession = _fixture.CreateTestDbSession()) - { - var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - Blogger johnnyFlash = repo.Single(456); - Assert.Equal(DateTime.UtcNow, johnnyFlash.ChangedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000))); - Assert.Equal(new SystemIdentity().Name, johnnyFlash.ChangedBy); - Assert.Equal("Johnny", johnnyFlash.FirstName); - Assert.Equal("Flash", johnnyFlash.LastName); - } - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs deleted file mode 100644 index 80e47e33..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainEvent : IDomainEvent - { - } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs deleted file mode 100644 index 6dbb4c02..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainModule : SimpleInjectorDomainModule - { - public ADomainModule(params Assembly[] domainAssemblies) - : base(domainAssemblies) - { - } - - protected override void Register(Container container, ScopedLifestyle scopedLifestyle) - { - base.Register(container, scopedLifestyle); - container.RegisterSingleton(); - container.Register(scopedLifestyle); - } - } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs deleted file mode 100644 index 0f300b10..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; -using SimpleInjector; -using Xunit; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests -{ - public class TheMisconfiguredSimpleInjectorCompositionRoot - { - [Fact] - public void ThrowsOnValidation() - { - var sut = new SimpleInjectorCompositionRoot(); - sut.RegisterModules(new BadModule()); - Assert.Throws(() => sut.Verify()); - } - - public class UnresolvableService - { - public UnresolvableService(Entity e) - { - throw new Exception($"This constructor should never be called, since the Entity {e?.GetType().Name} cannot be resolved by the container"); - } - } - - public class BadModule : SimpleInjectorModule - { - protected override void Register(Container container, ScopedLifestyle scopedLifestyle) - { - // this registration should be recognized as unresolvable during validation - container.Register(); - } - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Logging/TheLogManager.cs b/tests/Backend.Fx.Tests/Logging/TheLogManager.cs deleted file mode 100644 index ba1dcb02..00000000 --- a/tests/Backend.Fx.Tests/Logging/TheLogManager.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Security; -using Backend.Fx.Logging; -using FakeItEasy; -using Xunit; - -namespace Backend.Fx.Tests.Logging -{ - public class TheLogManager - { - public TheLogManager() - { - var loggerFactory = A.Fake(); - var logger = A.Fake(); - - A.CallTo(() => loggerFactory.Create()).Returns(logger); - } - - [Fact] - public void DoesNotThrowOnZeroConfig() - { - Exception ex = new SecurityException("very bad"); - var msg = "the log message"; - ILogger logger = LogManager.Create(); - - logger.Fatal(ex); - logger.Fatal(ex, msg); - logger.Fatal(msg); - logger.Error(ex); - logger.Error(ex, msg); - logger.Error(msg); - logger.Warn(ex); - logger.Warn(ex, msg); - logger.Warn(msg); - logger.Info(ex); - logger.Info(ex, msg); - logger.Info(msg); - logger.Debug(ex); - logger.Debug(ex, msg); - logger.Debug(msg); - logger.Trace(ex); - logger.Trace(ex, msg); - logger.Trace(msg); - - logger.TraceDuration(msg).Dispose(); - logger.DebugDuration(msg).Dispose(); - logger.InfoDuration(msg).Dispose(); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs deleted file mode 100644 index b6949ea3..00000000 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Backend.Fx.Tests.Patterns.DependencyInjection -{ - public class SimulatedException : Exception - { - public SimulatedException() : base("This exception was intentionally thrown by the unit test. If you see this message unexpectedly, probably the exception handling is broken") - { - - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs deleted file mode 100644 index fa7a61e5..00000000 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Diagnostics; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; -using FakeItEasy; -using Xunit; - -namespace Backend.Fx.Tests.Patterns.DependencyInjection -{ - public class TheBackendFxApplication - { - public TheBackendFxApplication() - { - _fakes = new DiTestFakes(); - - Func domainEventAggregatorFactory = () => null; - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)) - .Invokes((Func f) => domainEventAggregatorFactory = f); - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).ReturnsLazily(() => domainEventAggregatorFactory.Invoke()); - - Func messageBusScopeFactory = () => null; - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)) - .Invokes((Func f) => messageBusScopeFactory = f); - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).ReturnsLazily(() => messageBusScopeFactory.Invoke()); - - - _sut = new BackendFxApplication(_fakes.CompositionRoot, _fakes.MessageBus, A.Fake()); - } - - private readonly IBackendFxApplication _sut; - private readonly DiTestFakes _fakes; - - - [Fact] - public void CanWaitForBoot() - { - int bootTime = 200; - A.CallTo(() => _fakes.CompositionRoot.Verify()).Invokes(() => Thread.Sleep(bootTime)); - var sw = new Stopwatch(); - - Task.Factory.StartNew(() => _sut.BootAsync()); - sw.Start(); - Assert.True(_sut.WaitForBoot()); - Assert.True(sw.ElapsedMilliseconds >= bootTime); - } - - [Fact] - public void DisposesCompositionRootOnDispose() - { - _sut.BootAsync(); - _sut.Dispose(); - A.CallTo(() => _fakes.CompositionRoot.Dispose()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void ProvidesExceptionLoggingAsyncInvoker() - { - Assert.IsType(_sut.AsyncInvoker); - } - - [Fact] - public void ProvidesDomainEventAggregator() - { - using (IInjectionScope scope = _sut.CompositionRoot.BeginScope()) - { - var domainEventAggregator = scope.InstanceProvider.GetInstance(); - Assert.NotNull(domainEventAggregator); - } - - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void ProvidesExceptionLoggingInvoker() - { - Assert.IsType(_sut.Invoker); - } - - [Fact] - public void IntegratesWithMessageBus() - { - A.CallTo(() => _fakes.MessageBus.ProvideInvoker(A._)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void ProvidesMessageBusScope() - { - using (IInjectionScope scope = _sut.CompositionRoot.BeginScope()) - { - var messageBusScope = scope.InstanceProvider.GetInstance(); - Assert.NotNull(messageBusScope); - } - - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void RegistersInfrastructureModule() - { - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped, CurrentCorrelationHolder>()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped,CurrentIdentityHolder>()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped,CurrentTenantIdHolder>()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void VerifiesCompositionRootOnBoot() - { - _sut.BootAsync(); - A.CallTo(() => _fakes.CompositionRoot.Verify()).MustHaveHappenedOnceExactly(); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs deleted file mode 100644 index f1a82151..00000000 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Backend.Fx.Patterns.IdGeneration; -using FakeItEasy; -using Xunit; - -namespace Backend.Fx.Tests.Patterns.IdGeneration -{ - public class TheSequenceHiLoIdGenerator - { - public TheSequenceHiLoIdGenerator() - { - A.CallTo(() => _sequence.Increment).Returns(10); - _sut = new TestIdGenerator(_sequence); - } - - private readonly ISequence _sequence = A.Fake(); - private readonly SequenceHiLoIdGenerator _sut; - - private class TestIdGenerator : SequenceHiLoIdGenerator - { - public TestIdGenerator(ISequence sequence) : base(sequence) - { - } - } - - [Fact] - public void CallsSequenceNextValueOnBlockStart() - { - for (var i = 0; i < 10; i++) _sut.NextId(); - - A.CallTo(() => _sequence.GetNextValue()).MustHaveHappenedOnceExactly(); - _sut.NextId(); - A.CallTo(() => _sequence.GetNextValue()).MustHaveHappenedTwiceExactly(); - } - } -} \ No newline at end of file